diff --git a/.gitattributes b/.gitattributes index 7bb3d8153..f18431d9f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -9,7 +9,6 @@ website/ export-ignore .gitattributes export-ignore .gitignore export-ignore .phpcs.xml export-ignore -couscous.yml export-ignore docker-compose.yml export-ignore Makefile export-ignore phpstan.neon export-ignore diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml new file mode 100644 index 000000000..dd532ddf7 --- /dev/null +++ b/.github/workflows/website.yml @@ -0,0 +1,37 @@ +name: Website + +on: + push: + branches: [ 'website-v3' ] + +env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + +jobs: + + deploy: + runs-on: ubuntu-latest + permissions: + contents: read + defaults: + run: + working-directory: ./website + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # fetch all history for "latest modified time" + - run: npm install --global vercel@latest + - run: make src/pages/docs + - name: Pull Vercel Environment Information + run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} + - name: Build + run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} + env: + # CUSTOM_GITHUB_TOKEN_READ is a manually created GitHub token with appropriate permissions: + # - allowed to list contributors + # - allowed to list sponsors + # It wouldn't be possible to use the default GITHUB_TOKEN because it doesn't have the required permissions. + GITHUB_TOKEN: ${{ secrets.CUSTOM_GITHUB_TOKEN_READ }} + - name: Deploy + run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} diff --git a/Makefile b/Makefile index a2bdaf022..4ccc30d97 100644 --- a/Makefile +++ b/Makefile @@ -6,25 +6,6 @@ trigger_runtimes: runtime_build_status: aws codepipeline get-pipeline-state --name=bref-php-binary | jq ".stageStates[1].latestExecution.status" -# Generate and deploy the production version of the website using http://couscous.io -website: - # See http://couscous.io/ - couscous generate - netlify deploy --prod --dir=.couscous/generated -website-staging: - couscous generate - netlify deploy --dir=.couscous/generated - -# Run a local preview of the website using http://couscous.io -website-preview: - couscous preview - -website-assets: website/template/output.css -website/template/output.css: website/node_modules website/template/styles.css website/tailwind.config.js - cd website && NODE_ENV=production npx tailwind build template/styles.css -o template/output.css -website/node_modules: website/package.json website/package-lock.json - cd website && npm install - # Deploy the demo functions demo: serverless deploy @@ -35,4 +16,4 @@ layers.json: test-stack: serverless deploy -c tests/serverless.tests.yml -.PHONY: website website-preview website-assets demo layers.json test-stack +.PHONY: demo layers.json test-stack diff --git a/SECURITY.md b/SECURITY.md index 49719927b..7d21f003a 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -12,7 +12,7 @@ Version 1.7.x will only receive critical bug fixes and security fixes, as well P You are encouraged to upgrade to v2. -If you prefer to stay on v1 and need long term support for v1 (or need help upgrading), get in touch for enterprise support: https://bref.sh/#enterprise +If you prefer to stay on v1 and need long term support for v1 (or need help upgrading), get in touch for enterprise support: https://bref.sh/support ## Reporting a Vulnerability diff --git a/bref b/bref index 493d6f74f..f72b17f1d 100755 --- a/bref +++ b/bref @@ -21,7 +21,7 @@ switch ($argv[1] ?? '') { break; case 'cli': cliWarning(); - error('Since Bref 2.0, the "bref cli" command has been moved. Read https://bref.sh/docs/runtimes/console.html#usage'); + error('Since Bref 2.0, the "bref cli" command has been moved. Read https://bref.sh/docs/runtimes/console#usage'); case 'layers': cliWarning(); echo "Bref layer ARNs can be found here: https://runtimes.bref.sh/\n\n"; @@ -29,7 +29,7 @@ switch ($argv[1] ?? '') { exit(1); case 'local': cliWarning(); - error('Since Bref 2.0, the "bref local" command has been moved. Read https://bref.sh/docs/function/local-development.html\n'); + error('Since Bref 2.0, the "bref local" command has been moved. Read https://bref.sh/docs/local-development/event-driven-functions\n'); case 'dashboard': cliWarning(); echo "The Bref Dashboard is now available as a desktop application.\n\n"; diff --git a/couscous.yml b/couscous.yml deleted file mode 100644 index 02fe7bc4a..000000000 --- a/couscous.yml +++ /dev/null @@ -1,172 +0,0 @@ -template: - # Name of the directory containing the website template (default is "website") - directory: website/template - -exclude: - - demo - - runtime - - src - - template - - tests - - vendor - - website - # This special entry will ask Couscous to read the excluded directories from your ".gitignore" file - - "%gitignore%" - -scripts: - # Scripts to execute before generating the website - before: - - make website-assets - -menu: - intro: - - section: Getting started - items: - what-is-bref: - text: What is Bref and serverless? - url: /docs/ - installation: - text: Installation - url: /docs/installation.html - first-steps: - text: First steps - url: /docs/first-steps.html - runtimes-introduction: - text: Introduction to PHP runtimes - url: /docs/runtimes/ - - webHosting: - - section: 'Bref for web apps' - items: - web-apps: - text: Web apps on AWS Lambda - url: /docs/runtimes/http.html - websites: - text: Website assets - url: /docs/websites.html - title: Building complete websites with Bref - console-applications: - text: Console commands - url: /docs/runtimes/console.html - web-cron: - text: Cron commands - url: /docs/web-apps/cron.html - title: CLI cron tasks - web-local-development: - text: Local development - url: /docs/web-apps/local-development.html - web-docker: - text: Docker - url: /docs/web-apps/docker.html - - section: Frameworks - items: - laravel: - text: Laravel - url: /docs/frameworks/laravel.html - title: Learn how to deploy serverless Laravel applications - symfony: - text: Symfony - url: /docs/frameworks/symfony.html - title: Learn how to deploy serverless Symfony applications - - functions: - - section: 'Bref for event-driven functions' - items: - php-functions: - text: PHP functions on AWS Lambda - url: /docs/runtimes/function.html - typed-handlers: - text: Typed handlers - url: /docs/function/handlers.html - function-local-development: - text: Local development - url: /docs/function/local-development.html - cron-function: - text: Cron functions - url: /docs/function/cron.html - - other: - - section: Workflow - items: - deploy: - text: Deployment - url: /docs/deploy.html - monitoring: - text: Monitoring - url: /docs/monitoring.html - - section: Environment - items: - serverless-yml: - text: serverless.yml - url: /docs/environment/serverless-yml.html - title: Configure your application with the serverless.yml file - variables: - text: Variables - url: /docs/environment/variables.html - title: Configuring environment variables with Bref - php: - text: PHP - url: /docs/environment/php.html - title: Configuring PHP versions and options with Bref - storage: - text: Storage - url: /docs/environment/storage.html - title: Storing files and data with Bref on AWS Lambda - logs: - text: Logs - url: /docs/environment/logs.html - title: Managing logs with Bref on AWS Lambda - database: - text: Databases - url: /docs/environment/database.html - title: Using a database from AWS Lambda - database-planetscale: - text: Databases - PlanetScale - url: /docs/environment/database-planetscale.html - title: Using PlanetScale from AWS Lambda - custom-domains: - text: Custom domains - url: /docs/environment/custom-domains.html - title: Configuring custom domain names - performances: - text: Performance - url: /docs/environment/performances.html - title: Performance tuning and optimizations - - section: Upgrading - items: - v2: - text: Upgrading to v2 - url: /docs/upgrading/v2.html - title: Upgrading Bref applications to Bref v2 - - section: Learning - items: - course: - text: Course - url: https://serverless-visually-explained.com/?ref=bref-menu - title: Serverless Visually Explained - case-studies: - text: Case studies - url: /docs/case-studies.html - title: A collection of case studies of serverless PHP applications built using Bref. - community: - text: Community - url: /docs/community.html - title: Places where to learn and exchange about Bref. - - section: Ecosystem - items: - github: - text: GitHub - url: https://github.com/brefphp - title: Bref on GitHub - twitter: - text: Twitter - url: https://twitter.com/brefphp - title: Bref on Twitter - slack: - text: Slack - url: https://join.slack.com/t/brefworkspace/shared_invite/enQtNTcwMjU2NTcxNjAxLTIxYmM2MmRjMDkzYjdjYTNkMmE5NGI3YTcyZjc2ZGRjNTFmNjFmYzk5NWQ1YmVhMDkwNzExNzhjZThkZWM0ODE - title: Join the Slack community - dashboard: - text: Bref Dashboard - url: https://dashboard.bref.sh/?ref=bref - title: Bref Dashboard diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 31e20fa59..000000000 --- a/docs/README.md +++ /dev/null @@ -1,188 +0,0 @@ ---- -title: What is Bref and serverless? -current_menu: what-is-bref -introduction: An introduction to what serverless and Bref can offer for PHP applications. -next: - link: /docs/installation.html - title: Installation ---- - -Serverless means using cloud services that manage the servers for us. - -## Why serverless? - -When running PHP on a server, we must: - -- setup, configure and maintain that server, -- pay a fixed price for the server, -- scale the server(s) if we get more traffic. - -When running PHP serverless: - -- We do not need to set up servers, the cloud provider takes care of that. -- We pay only for what we use. -- Our application scales automatically. - -**Serverless provides more scalable, affordable and reliable architectures for less effort.** - -Serverless includes services like storage as a service, database as a service, message queue as a service, etc. One service in particular is interesting for us developers: *Function as a Service* (FaaS). - -FaaS is a way to run code where the hosting provider takes care of setting up everything, keeping the application available 24/7, scaling it up and down and we are only charged *while the code is actually executing*. - -## Why Bref? - -

-Bref aims to make running PHP applications simple. -

- -To reach that goal, Bref takes advantage of serverless technologies. However, while serverless is promising, there are many choices to make, tools to build and best practices to figure out. - -Bref's approach is to: - -- **simplify problems by removing choices** - - *instead of trying to address every need* -- **provide simple and familiar solutions** - - *instead of aiming for powerful custom solutions* -- **empower by sharing knowledge** - - *instead of hiding too much behind leaky abstractions* - -### What is Bref - -Bref (which means "brief" in french) comes as an open source Composer package and helps you deploy PHP applications to [AWS](https://aws.amazon.com) and run them on [AWS Lambda](https://aws.amazon.com/lambda/). - -Bref provides: - -- documentation -- PHP runtimes for AWS Lambda -- deployment tooling -- PHP frameworks integration - -The choice of AWS as serverless provider is deliberate: at the moment AWS is the leading hosting provider, it is ahead in the serverless space in terms of features, performance and reliability. - -Bref uses [the Serverless framework](https://serverless.com/) to configure and deploy serverless applications. Being the most popular tool, Serverless comes with a huge community, a lot of examples online and a simple configuration format. - -## Use cases - -Bref and AWS Lambda can be used to run many kind of PHP application, for example: - -- APIs -- workers -- batch processes/scripts -- websites - -Bref aims to support any PHP framework as well. - -If you are interested in real-world examples as well as cost analyses head over to the [**Case Studies** page](case-studies.md). - -## Maturity matrix - -The matrix below provides an overview of the "maturity level" for common PHP applications. - -This maturity level is a vague metric, however it can be useful to anticipate the effort and the limitations to expect for each scenario. While a green note doesn't mean that Bref and Lambda are silver bullets for the use case (there are no silver bullets), a red note doesn't mean this is impossible or advised against. - -This matrix will be updated as Bref and AWS services evolve over time. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SimplicityPerformanceReliability
- Jobs, Cron - - - - - - -
API - - - - - -
Website - - - - - -
Legacy application - - - - - -
- Is this documented and simple to achieve? - - Is performance acceptable? - - Is this scenario production-ready? -
- -
- Legend: - Good use case - Some drawbacks - Strong limitations -
- -- **Jobs, Cron** - - Jobs, cron tasks and batch processes are very good candidates for FaaS. The scaling model of AWS Lambda can lead to very high throughput in queue processing, and the pay-per-use billing model can sometimes result in drastic costs reduction. - - Using Bref, it is possible to implement cron jobs and queue workers using PHP. Bref also provides integration with popular queue libraries, like Laravel Queues and Symfony Messenger. - -- **API** - - APIs run on AWS Lambda without problems. Performance is now similar to what you could expect on traditional VPS. - -- **Website** - - Websites can run on AWS Lambda. Assets can be stored in S3 and served via Cloudfront. This is documented in the ["Websites" documentation](/docs/websites.md). Performance is as good as any server. - -- **Legacy application** - - Migrating a legacy PHP application to Bref and Lambda can be a challenge. One could expect to rewrite some parts of the code to make the application fit for Lambda (or running in containers in general). For example, file uploads and sessions often need to be adapted to work with the read-only filesystem. Cron tasks, scripts or asynchronous jobs must be made compatible with Lambda and SQS. - - Not impossible, but definitely not the easiest place to start. As a first step, you can follow the guidelines of [The Twelve-Factor App](https://12factor.net). - -## Getting started - -Get started with Bref by reading the [installation documentation](installation.md). diff --git a/docs/_meta.json b/docs/_meta.json new file mode 100644 index 000000000..88a96bb6b --- /dev/null +++ b/docs/_meta.json @@ -0,0 +1,41 @@ +{ + "index": "What is Bref and serverless?", + "-- Getting started": { + "type": "separator", + "title": "Getting started" + }, + "setup": "", + "laravel": "Laravel", + "symfony": "Symfony", + "default": "Other frameworks", + "-- How it works": { + "type": "separator", + "title": "How it works" + }, + "runtimes": "PHP runtimes for Lambda", + "serverless-costs": "Serverless costs", + "-- Workflow": { + "type": "separator", + "title": "Workflow" + }, + "deploy": "Deployment", + "local-development": "Local development", + "monitoring": "", + "-- Learn more": { + "type": "separator", + "title": "Learn more" + }, + "use-cases": "Use cases", + "environment": "Environment", + "upgrading": "Upgrading", + "-- Resources": { + "type": "separator", + "title": "Resources" + }, + "course": { + "title": "Serverless course", + "href": "https://serverless-visually-explained.com/?ref=bref-menu" + }, + "community": "", + "case-studies": "" +} \ No newline at end of file diff --git a/docs/case-studies.md b/docs/case-studies.md index 90890eb47..900385fec 100644 --- a/docs/case-studies.md +++ b/docs/case-studies.md @@ -1,9 +1,9 @@ --- -title: Case studies -current_menu: case-studies introduction: A collection of case studies of serverless PHP applications built using Bref. Learn about performance, costs and migrations from existing projects. --- +# Case studies + This page collects case studies of serverless PHP applications built with or migrated to Bref. These help learn for real use cases about costs, performance and migration efforts. diff --git a/docs/community.md b/docs/community.mdx similarity index 54% rename from docs/community.md rename to docs/community.mdx index da50de736..3e8f90076 100644 --- a/docs/community.md +++ b/docs/community.mdx @@ -1,14 +1,14 @@ ---- -title: Community -current_menu: community -introduction: A collection of links to places where to discuss and learn about Bref. ---- +import { NextSeo } from 'next-seo'; + + + +# Community To report bugs you can head over to the [GitHub Bref repository](https://github.com/brefphp/bref). -For support and general discussions, [open a GitHub discussion](https://github.com/brefphp/bref/discussions). +For community support and general discussions, [open a GitHub discussion](https://github.com/brefphp/bref/discussions). -You can also join the [Slack community](https://join.slack.com/t/brefworkspace/shared_invite/enQtNTcwMjU2NTcxNjAxLTIxYmM2MmRjMDkzYjdjYTNkMmE5NGI3YTcyZjc2ZGRjNTFmNjFmYzk5NWQ1YmVhMDkwNzExNzhjZThkZWM0ODE) to discuss Bref and exchange with the community (please open an issue if the link doesn't work). +You can also join the [Slack community](https://bref.sh/slack) to discuss Bref and exchange with the community (please open an issue if the link doesn't work). On Twitter, follow [@brefphp](https://twitter.com/brefphp) to get news about Bref. @@ -24,7 +24,7 @@ You can subscribe to these newsletters to keep up to date with what's happening - [Serverless PHP](https://serverless-php.news/) - A monthly newsletter about serverless news related to PHP. + A newsletter about serverless news related to PHP. - [Off by None](https://www.jeremydaly.com/newsletter/) A very packed weekly newsletter about serverless news in general. diff --git a/docs/default/_meta.json b/docs/default/_meta.json new file mode 100644 index 000000000..857fc087f --- /dev/null +++ b/docs/default/_meta.json @@ -0,0 +1,4 @@ +{ + "getting-started": "", + "cli-commands": "CLI commands" +} \ No newline at end of file diff --git a/docs/default/cli-commands.mdx b/docs/default/cli-commands.mdx new file mode 100644 index 000000000..db8b8dc24 --- /dev/null +++ b/docs/default/cli-commands.mdx @@ -0,0 +1,28 @@ +# CLI commands + +We can run CLI commands and scripts on AWS Lambda by deploying a "console" function with `serverless.yml`. + +```yml +functions: + cli: + handler: the-php-script-to-run.php + runtime: php-81-console +``` + +The function uses the [Console runtime](../runtimes/console.mdx). + +To execute the script on Lambda, run the command below: + +```bash +serverless bref:cli +``` + +We can also pass arguments to the script: + +```bash +serverless bref:cli --args="extra command line arguments and options" +``` + +Our script will be invoked inside AWS Lambda and the result will be printed to the console. + +To learn more, read [the "Console" guide](../runtimes/console.mdx). diff --git a/docs/default/getting-started.mdx b/docs/default/getting-started.mdx new file mode 100644 index 000000000..2948c498c --- /dev/null +++ b/docs/default/getting-started.mdx @@ -0,0 +1,95 @@ +import { Cards, Card } from 'nextra/components'; +// Path relative to the copy in the `website/` folder +import { LaravelIcon } from '../../../components/icons/LaravelIcon'; +import { SymfonyIcon } from '../../../components/icons/SymfonyIcon'; +import { NextSeo } from 'next-seo'; + + + +# Getting started - Bref with any framework + +This guide will help you deploy your first PHP application on AWS Lambda. The instructions below can be adapted to work with any framework. + +If you are using Laravel or Symfony, check out the dedicated guides instead: + + + } title="Get started with Laravel" arrow="true" href="/docs/laravel/getting-started" /> + } title="Get started with Symfony" arrow="true" href="/docs/symfony/getting-started" /> + + +## Setup + +First, **follow the [Setup guide](../setup.mdx)** to create an AWS account and install the necessary tools. + +Next, in an empty directory, install Bref using Composer: + +```bash +composer require bref/bref +``` + +Make sure that the version of Bref that was installed is 1.0 or greater. + +Then let's start by initializing a new project by running: + +```bash +vendor/bin/bref init +``` + +Accept all the defaults by pressing "Enter". The following files will be created in your project: + +- `index.php` contains the code of your application +- `serverless.yml` contains the configuration for deploying on AWS + +You are free to edit `index.php`. + +To deploy an existing application, you can delete `index.php` and edit `serverless.yml` to point to your existing index file (for example it may be another file like `public/index.php`). You can also create the `serverless.yml` file manually: + +```yml filename="serverless.yml" +service: app +provider: + name: aws + region: us-east-1 + +functions: + web: + handler: index.php + runtime: php-81-fpm + events: + - httpApi: '*' + +package: + patterns: # Exclude files from deployment + - '!node_modules/**' + - '!tests/**' + +plugins: + - ./vendor/bref/bref +``` + +## Deployment + +To deploy, run: + +```bash +serverless deploy +``` + +Once the command finishes, it should print a URL like this one: + +```sh +https://3pjp2yiw97.execute-api.us-east-1.amazonaws.com +``` + +Open this URL and you should see your application: `index.php` is running on Lambda! + +Congrats on creating your first serverless application 🎉 + +To learn more about deployments, head over the [Deployment guide](../deploy.mdx). + +## Troubleshooting + +In case your application is showing a blank page after being deployed, [have a look at the logs](../environment/logs.md). + +## Website assets + +Have a look at the [Website guide](../use-cases/websites.mdx) to learn how to deploy a website with assets. diff --git a/docs/deploy.md b/docs/deploy.mdx similarity index 53% rename from docs/deploy.md rename to docs/deploy.mdx index 35af31ee2..6dde9bb39 100644 --- a/docs/deploy.md +++ b/docs/deploy.mdx @@ -1,21 +1,24 @@ ---- -title: Deployment -current_menu: deploy ---- +import { Callout } from 'nextra/components'; -Bref recommends using [the Serverless framework](https://serverless.com/) to deploy your serverless application. This page will show you how. +# Deployment + +Bref is designed out of the box to deploy using [the Serverless Framework](https://serverless.com/). + +Bref can also work with any other deployment tool: Terraform, CloudFormation, SAM, [AWS CDK](https://github.com/brefphp/constructs), Pulumi… However, the documentation and user experience is optimized for Serverless Framework. ## Deploying manually -To deploy an application configured with `serverless.yml` to AWS, run: +To deploy to AWS an application configured with `serverless.yml`, run: ```bash serverless deploy ``` -> A `.serverless/` directory will be created. You can add it to `.gitignore`. -> -> Want to get an overview of your deployed application? Check out the [Bref Dashboard](https://dashboard.bref.sh/?ref=bref). +A `.serverless/` directory will be created. You can add it to `.gitignore`. + + + Want to get an overview of your deployed application? Check out the [Bref Dashboard](https://dashboard.bref.sh/?ref=bref). + ## Deploying for production @@ -57,7 +60,7 @@ It is possible to deploy different stages in different AWS accounts (to lock dow ## Automating deployments -If you are using Gitlab CI, Travis CI, CircleCI or any tool of the sort you will want to automate the deployment to something like this: +If you are using GitHub Actions, Gitlab CI, CircleCI, or any tool of the sort you will want to automate the deployment to something like this: ```bash # Install Composer dependencies optimized for production @@ -87,7 +90,11 @@ provider: ... ``` -> If you are a first time user, using the `us-east-1` region (the default region) is recommended for the first projects. It simplifies commands and avoids a lot of mistakes when discovering AWS. + + If you are a first time user, using the `us-east-1` region (the default region) is recommended for the first projects. It simplifies commands and avoids a lot of mistakes when discovering AWS. + + I mean really… I can't count how many times a command failed or an AWS page looked empty because I was in the wrong region. + ## Deletion @@ -97,23 +104,42 @@ To delete the whole application you can run: serverless remove ``` +Note that this command, like `serverless deploy`, is for a specific stage. If you want to delete all stages you will have to run the command once per stage. + ## How it works ### CloudFormation stacks -The `serverless deploy` command will deploy everything via a **[CloudFormation](https://aws.amazon.com/cloudformation/) stack**. A stack is nothing more than a bunch of things that compose an application: +The `serverless deploy` command will deploy everything via a **[CloudFormation](https://aws.amazon.com/cloudformation/) stack**. A "stack" is nothing more than a bunch of things that compose an application: - lambda functions - S3 buckets - databases +- etc. Stacks make it easy to group those resources together: the whole stack is updated at once on deployments, and if you delete the stack all the resources inside are deleted together too. Clean and simple. -All of this is great except CloudFormation configuration is complex. This is where *Serverless* helps. +All of this is great except CloudFormation configuration is complex. This is where Serverless Framework helps. + +### Zero-downtime deployments + +CloudFormation deploys using the [blue/green deployment strategy](https://docs.aws.amazon.com/whitepapers/latest/overview-deployment-options/bluegreen-deployments.html). + +This means that when you deploy, a new version of your code is deployed alongside the old one. Once the new version is ready, the traffic switches to the new version. If the deployment fails at any point, the traffic stays on the old version. + +#### Limits to blue/green deployment + +As soon as you introduce **asynchronous behaviors** (e.g. background jobs with SQS, event-driven microservices…) you may have in-flight messages (SQS jobs, EventBridge events…) created by the old version of your code that will be processed by the new version of your code. + +Code that handles asynchronous events must be able to handle messages created by older versions of the code. + +#### Database migrations + +Zero-downtime deployments mean that database migrations must run when code is running in production. That means either before or after the deployment (traffic switch) happens, and having a DB migration strategy compatible with that. ### `serverless.yml` -The *Serverless* framework offers a simple configuration format. This is what you are using if you use Bref. That configuration is written in your project in a `serverless.yml` file. +Serverless Framework offers a simple configuration format. This is what you are using if you use Bref. That configuration is written in your project in a `serverless.yml` file. You can [learn more about that configuration format here](environment/serverless-yml.md). diff --git a/docs/deploy/_meta.json b/docs/deploy/_meta.json new file mode 100644 index 000000000..ccbea5e52 --- /dev/null +++ b/docs/deploy/_meta.json @@ -0,0 +1,4 @@ +{ + "docker": "Deploying Docker images", + "aws-cdk": "Deploying with AWS CDK" +} \ No newline at end of file diff --git a/docs/deploy/aws-cdk.mdx b/docs/deploy/aws-cdk.mdx new file mode 100644 index 000000000..1a3862784 --- /dev/null +++ b/docs/deploy/aws-cdk.mdx @@ -0,0 +1,35 @@ +import { RemoteContent } from 'nextra/data' +import { buildDynamicMDX, buildDynamicMeta } from 'nextra/remote' + +export const getStaticProps = async ({ params }) => { + const token = process.env.GITHUB_TEST_REMOTE_MDX + const res = await fetch( + 'https://api.github.com/repos/brefphp/constructs/contents/README.md', + { + headers: { + Accept: 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + ...(token && { Authorization: `Bearer ${token}` }) + } + } + ) + const page = await res.json() + if (!page.content) { + throw new Error( + `Error while fetch data from GitHub.\n${JSON.stringify(page, null, 4)}` + ) + } + const content = Buffer.from(page.content, 'base64').toString('utf8'); + return { + props: { + ...(await buildDynamicMDX(content, { + defaultShowCopyCode: true + })), + ...(await buildDynamicMeta()) + }, + // Don't update the page dynamically + revalidate: false, + } +} + + diff --git a/docs/deploy/docker.mdx b/docs/deploy/docker.mdx new file mode 100644 index 000000000..7d09a5492 --- /dev/null +++ b/docs/deploy/docker.mdx @@ -0,0 +1,123 @@ +import { Callout } from 'nextra/components'; + +# Deploying Docker images + + + Are you starting with Bref? Deploy **without Docker first**. It's easier and faster. You can always switch to Docker later. + + [Read the "Deployment" guide](../deploy) + + +By default, Bref deploys to AWS Lambda using zip archives, which Lambda will run in an Amazon Linux environment. This is how AWS Lambda works out of the box, and it works great. + +However, AWS Lambda also supports **deploying and running Docker container images**. + +We recommend Docker **as a last resort**, as it is less practical and usually comes with slightly worse cold starts. Yes, Docker is great and probably sounds familiar, but is often not worth it on Lambda. + +You should consider deploying using Docker when: + +- Your code size is [larger than the 250MB limit when unzipped](../environment/storage.md) +- You reached the limit of 5 Lambda layers (e.g. for extra PHP extensions) +- You need custom binaries/resources installed locally (e.g. mysqldump, wkhtmltopdf) + + + This documentation page assumes that you have familiarized yourself with Bref first. + + +## Docker Image + +Bref helps you deploy using Docker images by offering base images that work on AWS Lambda. Here is an example of a Dockerfile you can use: + +```dockerfile filename="Dockerfile" +FROM bref/php-81-fpm:2 + +# Copy the source code in the image +COPY .. /var/task + +# Configure the handler file (the entrypoint that receives all HTTP requests) +CMD ["public/index.php"] +``` + +The `CMD` instruction let us specify the entrypoint that will handle all requests. This is the equivalent of the `handler` in the `serverless.yml` file. + + + Always specify the major version of the Bref image you want to use. That avoids breaking changes when a new major version is released. + + For example `bref/php-81-fpm:2` points to Bref v2. + + +Bref offers the following base images: + +- `bref/php-xx-fpm:2`: PHP-FPM to run HTTP applications +- `bref/php-xx-console:2`: to run PHP CLI commands +- `bref/php-xx:2`: to run [PHP functions](../runtimes/function.mdx) + +### Extra PHP extensions + +You can enable additional PHP extensions by pulling them from [Bref Extra Extensions](https://github.com/brefphp/extra-php-extensions): + +```dockerfile filename="Dockerfile" {3-4} +FROM bref/php-81-fpm:2 + +COPY --from=bref/extra-redis-php-81:1 /opt /opt +COPY --from=bref/extra-gmp-php-81:1 /opt /opt + +COPY . /var/task + +CMD ["public/index.php"] +``` + + + Like the Bref images, always specify the major version of the Bref Extra Extensions images: `bref/extra-*:1` points to Bref Extra Extensions v1. + + Note that Bref v2 is compatible with Bref Extra Extensions v1 (yes that's confusing, sorry about that, we will fix that in Bref v3 to have matching versions). + + +## Deployment + +The Serverless Framework supports deploying Docker images to Lambda: + +```yml filename="serverless.yml" {5-9,13-14} +service: bref-with-docker + +provider: + name: aws + ecr: + images: + hello-world: + # Path to the `Dockerfile` file + path: ./ + +functions: + hello: + image: + name: hello-world + events: + - httpApi: '*' +``` + +Instead of having a `handler` and a `runtime`, we'll declare an `image`. In the `provider` block, we'll declare the Docker images that we want to build and deploy. + +When running `serverless deploy`, the CLI will: + +- Build the Docker images according to their specified `path` +- Create an AWS ECR repository called `serverless-{service}-{env}` +- Authenticate against your ECR account +- Push the newly built Docker image +- Deploy the Lambda function pointing to the Docker image + +Note that you can create multiple images in the same `serverless.yml` file. For example, you can have one image for the HTTP handler and another image for a worker. + +## Filesystem + +Like with non-Docker deployments, the filesystem for Docker on AWS Lambda is also readonly with a limited disk space under `/tmp` for read/write. + +The `/tmp` folder will always be empty on cold starts. Avoid writing content to `/tmp` in your Dockerfile because that content will **not be available** for your Lambda function. + +[Read more about file storage in Lambda](../environment/storage.md). + +## Docker Registry + +AWS Lambda only support AWS ECR as the source location for Docker images. + +AWS Lambda will use the image digest as the unique identifier. This means that even if you overwrite the exact same tag on ECR, your lambda will still run the previous image code until you actually redeploy using the new image. diff --git a/docs/environment/_meta.json b/docs/environment/_meta.json new file mode 100644 index 000000000..e3804712a --- /dev/null +++ b/docs/environment/_meta.json @@ -0,0 +1,13 @@ +{ + "serverless-yml": "serverless.yml", + "variables": "Environment variables", + "php": "", + "storage": "", + "logs": "", + "database": "Databases", + "database-planetscale": "Databases - PlanetScale", + "database-public": { + "display": "hidden" + }, + "performances": "Performance" +} \ No newline at end of file diff --git a/docs/environment/custom-domains.md b/docs/environment/custom-domains.md deleted file mode 100644 index bd41478b8..000000000 --- a/docs/environment/custom-domains.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Custom domain names -current_menu: custom-domains -introduction: Configure custom domain names for your web applications. ---- - -API Gateway generates random domain names for our applications: - -``` -https://.execute-api..amazonaws.com/ -``` - -It is possible to replace those URLs by a custom domain. - -> These guides assume you already own the domain name you will want to use. - -## Custom domains for HTTP lambdas - -The first thing to do is register the domain in **ACM** (AWS Certificate Manager) to get a HTTPS certificate. This step is not optional. - -- open [this link](https://console.aws.amazon.com/acm/home?region=us-east-1#/wizard/) or manually go in the ACM Console and click "Request a new certificate" in the `us-east-1` region (the region used for global "edge" certificates) -- add your domain name and click "Next" -- choose the domain validation of your choice - - domain validation will require you to add CNAME entries to your DNS configuration - - email validation will require you to click a link you will receive in an email sent to `admin@your-domain.com` - -After validating the domain and the certificate we can now link the custom domain to our application via API Gateway. - -- open [API Gateway's "Custom Domain" configuration](https://console.aws.amazon.com/apigateway/main/publish/domain-names) -- **switch to the region of your application** -- click "Create" -- enter your domain name, select the certificate you created above and save -- edit the domain that was created -- click "Configure API mappings" to add an "API mapping": select your application and the `$default` stage (or `dev` in some cases), for example: - - ![](custom-domains-path-mapping.png) -- after saving the "API mappings", find the `API Gateway domain name` in the "Configurations" tab -- create a CNAME entry in your DNS to point your domain name to this domain - -After waiting for the DNS change to propagate (sometimes up to several hours) your website is now accessible via your custom domain. - -> You can also take a look at the plugin [serverless-domain-manager](https://www.serverless.com/plugins/serverless-domain-manager). -> It handles the custom domain creation and optionally adds the Route53 record if asked. It is still necessary to create the ACM certificate manually. -> -> A basic implementation is proposed here : https://www.serverless.com/blog/serverless-api-gateway-domain#create-a-custom-domain-in-api-gateway - - -## Custom domains for static files on S3 - -Some applications serve static files hosted on AWS S3. You can read the [Websites](/docs/websites.md) documentation to learn more. - -If you want to host a fully static website with HTML files or a Single Page Application (eg. built with Vue, React or Angular) -you may be interested in the Static Website construct of the Lift plugin. - -This allows to configure custom domains, root domain to `www` redirects and more. Check out the official documentation for more details. diff --git a/docs/environment/database-planetscale.md b/docs/environment/database-planetscale.mdx similarity index 89% rename from docs/environment/database-planetscale.md rename to docs/environment/database-planetscale.mdx index ccd0284e4..f5a7c6444 100644 --- a/docs/environment/database-planetscale.md +++ b/docs/environment/database-planetscale.mdx @@ -1,8 +1,9 @@ ---- -title: Using PlanetScale with Bref on AWS Lambda -current_menu: database-planetscale -introduction: Configure Bref to use a PlanetScale database in your PHP application on AWS Lambda. ---- +import { Callout } from 'nextra/components'; +import { NextSeo } from 'next-seo'; + + + +# Using PlanetScale with Bref on AWS Lambda [PlanetScale](https://planetscale.com/) is a hosted serverless MySQL database based on the Vitess engine ([learn more](https://planetscale.com/docs/concepts/what-is-planetscale)). @@ -11,7 +12,7 @@ Amongst other features, it offers the following benefits compared to running a d - Simple to set up: no VPC (virtual private network) to set up, no instances to configure. - Runs [the Vitess clustering system](https://planetscale.com/blog/vitess-for-the-rest-of-us), which offers better scalability and supports a lot more concurrent connections [via built-in connection pooling](https://planetscale.com/blog/one-million-connections). - Offers a [free database in the Hobby plan](https://planetscale.com/pricing), and paid plans are usage-based. -- Since it does not require a VPC, we do not need to set up and pay [for a NAT Gateway](database.md#accessing-the-internet). +- Since it does not require a VPC, we do not need to set up and pay [for a NAT Gateway](database.mdx#accessing-the-internet). One extra feature worth mentioning is [the branching concept](https://planetscale.com/docs/concepts/branching): it enables testing schema changes before deploying them in production without downtime. @@ -23,7 +24,9 @@ Then, create a database in the same region as your Bref application. ![](./database/planetscale-create.png) -> The database is created with an initial development branch: `main`. PlanetScale [has a branching concept](https://planetscale.com/docs/concepts/branching) that lets you test schema changes in a development branch, then promote it to production, or even create new branches (isolated copies of the production schema) off of production to use for development. + + The database is created with an initial development branch: `main`. PlanetScale [has a branching concept](https://planetscale.com/docs/concepts/branching) that lets you test schema changes in a development branch, then promote it to production, or even create new branches (isolated copies of the production schema) off of production to use for development. + You can now click the **Connect** button and select "Connect with: PHP (PDO)". That will let you retrieve the host, database name, user and password. @@ -51,13 +54,15 @@ In Bref, the file is located here: `/opt/bref/ssl/cert.pem`. ## Laravel -_This guide assumes you have already set up a Laravel application by following [the Bref documentation for Laravel](../frameworks/laravel.md)._ + + This guide assumes you have already set up a Laravel application by following [the Bref documentation for Laravel](../laravel/getting-started.mdx). + To configure Laravel to use the PlanetScale database, you need to set it up via environment variables. If you deploy a `.env` file, set up the following variables: -```bash +```bash filename=".env" DB_CONNECTION=mysql DB_HOST= DB_PORT=3306 @@ -70,7 +75,7 @@ MYSQL_ATTR_SSL_CA=/opt/bref/ssl/cert.pem If you don't deploy the `.env` file, you can configure the variables in `serverless.yml`: -```yaml +```yml filename="serverless.yml" provider: # ... environment: @@ -82,7 +87,7 @@ provider: MYSQL_ATTR_SSL_CA: /opt/bref/ssl/cert.pem ``` -Note that the `DB_PASSWORD` value is sensitive and can be set up as a secret via SSM. Read about [Secret variables](./variables.md#secrets) to learn more. +Note that the `DB_PASSWORD` value is sensitive and can be set up as a secret via SSM. Read about [Secret variables](./variables.mdx#secrets) to learn more. Don't forget to deploy the changes: @@ -102,7 +107,9 @@ That's it! Our database is ready to use. ## Symfony -_This guide assumes you have already set up a Symfony application by following [the Bref documentation for Symfony](../frameworks/symfony.md)._ + + This guide assumes you have already set up a Symfony application by following [the Bref documentation for Symfony](../symfony/getting-started.mdx). + First, make sure you have installed Doctrine, or [follow these docs to do so](https://symfony.com/doc/current/doctrine.html#installing-doctrine). @@ -110,24 +117,24 @@ To configure Symfony to use the PlanetScale database, you need to set it up via If you deploy a `.env` file, set up the following variables: -```bash +```bash filename=".env" DATABASE_URL="mysql://:@:3306/?serverVersion=8.0" ``` If you don't deploy the `.env` file, you can configure the variables in `serverless.yml`: -```yaml +```yml filename="serverless.yml" provider: # ... environment: DATABASE_URL: ${ssm:/my-app/database-url} ``` -Note that the `DATABASE_URL` value is sensitive and can be set up as a secret via SSM. Read about [Secret variables](./variables.md#secrets) to learn more. +Note that the `DATABASE_URL` value is sensitive and can be set up as a secret via SSM. Read about [Secret variables](./variables.mdx#secrets) to learn more. Finally, edit the `config/packages/doctrine.yaml` configuration file to set up [the SSL connections](https://planetscale.com/docs/concepts/secure-connections): -```yaml +```yml filename="config/packages/doctrine.yaml" {6} doctrine: dbal: url: '%env(resolve:DATABASE_URL)%' @@ -182,14 +189,14 @@ mysqldump -u -p -h --set-gtid-purged=OFF --no-tables Next, edit `schema.sql` to remove all foreign key constraints ([learn more](https://planetscale.com/docs/learn/operating-without-foreign-key-constraints)), for example: -```diff +```diff filename="schema.sql" CREATE TABLE products ( id INT NOT NULL, category_id INT, PRIMARY KEY (id), -+ KEY category_id_idx (category_id) - KEY category_id_idx (category_id), - CONSTRAINT `category_fk` FOREIGN KEY (category_id) REFERENCES category(id) ++ KEY category_id_idx (category_id) ); ``` @@ -220,7 +227,7 @@ If the production branch has the "Safe Migrations" feature **disabled**, you can This strategy implies either: -- accepting downtime on deployment, for example by using [Laravel's maintenance mode](../frameworks/laravel.md#maintenance-mode) (put the app offline, deploy, run migrations, then put the app back online) +- accepting downtime on deployment, for example by using [Laravel's maintenance mode](../laravel/maintenance-mode.mdx) (put the app offline, deploy, run migrations, then put the app back online) - or always writing backward-compatible DB migrations This option works well for applications with low traffic or in early development. For high-traffic applications, using "Safe Migrations" is recommended instead. @@ -250,7 +257,7 @@ To deploy DB migrations in production, you can work with two environments: - A **production** environment: our application deployed in the `prod` stage and configured to use the `production` PlanetScale branch. - A **dev** environment: our application deployed in the `dev` stage and configured to use the `development` PlanetScale branch. -You can [deploy our applications to different stages](../deploy.md#stages) via the `--stage` option. Each stage is completely isolated from the others. +You can [deploy our applications to different stages](../deploy.mdx#stages) via the `--stage` option. Each stage is completely isolated from the others. ```bash # Deploy the "dev" environment: @@ -262,7 +269,7 @@ serverless deploy --stage=prod You want each "stage" of our application to connect to a different PlanetScale branch. You can achieve that in `serverless.yml` via [stage parameters](https://www.serverless.com/framework/docs/guides/parameters#stage-parameters): -```yaml +```yml filename="serverless.yml" provider: # ... environment: @@ -293,8 +300,8 @@ Now that the environments are set up, you can apply the following workflow for D 1. Deploy your code changes and migrations in the development stage. 1. Apply DB migrations in the **development** environment (drop a column, add a table, etc.): - - If you use Laravel, run DB migrations [via the `artisan` function](../frameworks/laravel.md#laravel-artisan): `serverless bref:cli --stage=dev --args="migrate"` - - If you use Symfony, run DB migrations [via the `console` function](../frameworks/symfony.md#console): `serverless bref:cli --stage=dev --args="doctrine:migrations:migrate"` + - If you use Laravel, run DB migrations [via the `artisan` function](../laravel/getting-started.mdx#laravel-artisan): `serverless bref:cli --stage=dev --args="migrate"` + - If you use Symfony, run DB migrations [via the `console` function](../symfony/getting-started.mdx#symfony-console): `serverless bref:cli --stage=dev --args="doctrine:migrations:migrate"` - If you don't use any framework, run DB queries [via the `pscale` CLI](https://planetscale.com/docs/reference/planetscale-cli). 1. Test changes in the development environment to make sure everything works correctly. 1. Create a deploy request. diff --git a/docs/environment/database-public.md b/docs/environment/database-public.md index ac37195d0..1dbb34b0f 100644 --- a/docs/environment/database-public.md +++ b/docs/environment/database-public.md @@ -1,12 +1,10 @@ --- -title: Exposing a RDS database on the internet introduction: Configure RDS to expose a RDS database publicly so that you can access it from your computer. -previous: - link: /docs/environment/database.html - title: Databases --- -[◀ Back to the "Databases" documentation](database.md). +# Exposing a RDS database on the internet + +[◀ Back to the "Databases" documentation](database.mdx). --- diff --git a/docs/environment/database.md b/docs/environment/database.mdx similarity index 73% rename from docs/environment/database.md rename to docs/environment/database.mdx index 16f0f134e..ad16fbd1f 100644 --- a/docs/environment/database.md +++ b/docs/environment/database.mdx @@ -1,8 +1,9 @@ ---- -title: Using a database -current_menu: database -introduction: Configure Bref to use a database in your PHP application on AWS Lambda. ---- +import { Callout } from 'nextra/components'; +import { NextSeo } from 'next-seo'; + + + +# Using a database AWS offers the [RDS](https://aws.amazon.com/rds/) service to run MySQL and PostgreSQL databases. @@ -13,28 +14,30 @@ Here are some of the database services offered by RDS: - [Aurora MySQL/PostgreSQL](https://aws.amazon.com/rds/aurora/): closed-source database with MySQL/PostgreSQL compatibility - [Aurora Serverless MySQL/PostgreSQL](https://aws.amazon.com/rds/aurora/serverless/): similar to Aurora but scales automatically on-demand -> Aurora Serverless can be configured to scale down to 0 instances when unused (which costs $0), however be careful with this option: the database can take up to 30 seconds to un-pause. + + Aurora Serverless v1 can be configured to scale down to 0 instances when unused (which costs $0), however be careful with this option: the database can take up to 30 seconds to un-pause. Aurora Serverless v2 cannot scale down to 0 instances. + All RDS databases can be setup with Lambda in two ways: 1. the database can be made publicly accessible and protected by a username and password 2. the database can be made inaccessible from internet by putting it in a private network (aka [VPC](https://aws.amazon.com/vpc/)) -> Note that Aurora Serverless [cannot be made publicly accessible](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless.html), only the second option is possible. + + Aurora Serverless [cannot be made publicly accessible](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless.html), only the second option is possible. + While the first solution is simpler, the second is more secure. Using a VPC also comes with a few limitations that are detailed below. This page documents how to create databases using VPC (the reliable and secure solution). If you want to skip using a VPC you can read the instructions in the "Accessing the database from your machine" section. -> If you use Aurora Serverless, you can also use the [RDS Data API](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html). SQL queries are executed through an HTTP API instead of the traditional MySQL/PostgreSQL connection. To help you, the [dbal-rds-data](https://github.com/Nemo64/dbal-rds-data) library is a Doctrine DBAL driver. Please note that the library and the API itself are new and experimental. - ## Accessing the internet -> ⚠️ WARNING ⚠️ -> -> If your Lambda function has **timeouts**, please read this section. -> -> If you plan on using a database, please read this section. + + If your Lambda function has **timeouts**, please read this section. + + If you plan on using a database, please read this section. + A database inside a [VPC](https://aws.amazon.com/vpc/) is isolated from the internet. Lambda must run in the VPC to access the database, but it will lose access the internet (for example external APIs, and other AWS services). @@ -46,9 +49,9 @@ Because of that, you may see errors like this: > Task timed out after 28 seconds -To restore internet access for a lambda you will need to create a NAT Gateway in the VPC: you can follow [this tutorial](https://medium.com/@philippholly/aws-lambda-enable-outgoing-internet-access-within-vpc-8dd250e11e12), use [the serverless VPC plugin](https://github.com/smoketurner/serverless-vpc-plugin), or use the complete example in [Serverless Visually Explained](https://serverless-visually-explained.com/). +To restore internet access for a lambda you need to create a NAT Gateway in the VPC. This cannot be solved via security group or subnet configuration, only a NAT Gateway can solve this. You can follow [this tutorial](https://medium.com/@philippholly/aws-lambda-enable-outgoing-internet-access-within-vpc-8dd250e11e12), use [the serverless VPC plugin](https://github.com/smoketurner/serverless-vpc-plugin), or use the complete example in [Serverless Visually Explained](https://serverless-visually-explained.com/). -Watch out, a NAT Gateway will increase costs (starts at $27 per month). Note that you can use one VPC and one NAT Gateway for multiple projects. +Watch out, a NAT Gateway will increase costs (starts at $27 per month). Note that you can use one VPC and one NAT Gateway for multiple projects. To reduce costs, you can also use [a NAT instance](https://fck-nat.dev/) (starts at $3 per month). When possible, an alternative to NAT Gateways is to split the work done by a lambda in 2 lambdas: one in the VPC that accesses the database and one outside that accesses the external API. But that's often hard to implement in practice. @@ -56,7 +59,7 @@ Finally, another free alternative to NAT Gateway is to access AWS services by cr ## Creating a database -On the [RDS console](https://console.aws.amazon.com/rds/home): +In the [RDS console](https://console.aws.amazon.com/rds/home): - click "Create database" - select the type of database you want to create and fill in the form @@ -72,26 +75,28 @@ Tips to better control costs: To retrieve the information needed to let AWS Lambda access the database go into [the RDS dashboard](https://console.aws.amazon.com/rds/home#databases:) (or the [Bref Dashboard](https://dashboard.bref.sh/?ref=bref)) and open the database you created. -> It may take some minutes for the database to be created. + + It may take a few minutes for the database to be created. + Find: -- the "endpoint", which is the hostname of the database (this information is available only after the database creation has completed) +- The "endpoint", which is the hostname of the database (this information is available only after the database creation has completed) ℹ️ Instead of connecting via a socket, via `localhost` or an IP address, PHP will connect to MySQL via this hostname. -- the "security group ID" (in the "VPC security groups" section), which looks like `sg-03f68e1100481622b` +- The "security group ID" (in the "VPC security groups" section), which looks like `sg-03f68e1100481622b` ℹ️ A [security group](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_SecurityGroups.html) is a firewall that restricts access to/from the VPC using "Inbound rules" and "Outbound rules". -- the list of "subnets", which look like `subnet-12f4130e` (there are several subnets) +- The list of "subnets", which look like `subnet-12f4130e` (there are several subnets) ℹ️ An AWS region is divided in [availability zones](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html) (different data centers): there is usually one subnet per availability zone. Put these information in `serverless.yml` in your function configuration ([read more about this in the Serverless documentation](https://serverless.com/framework/docs/providers/aws/guide/functions/#vpc-configuration)): -```yaml +```yml filename="serverless.yml" functions: hello: - ... + # ... vpc: securityGroupIds: - sg-03f68e1100481622b @@ -117,9 +122,9 @@ For example a PDO connection string could be: mysql://user:password@dbname.e2sctvp0nqos.us-east-1.rds.amazonaws.com/dbname ``` -To learn how to properly store this connection string in your configuration head over to the ["Secrets" section of the Variables documentation](/docs/environment/variables.md#secrets). +To learn how to properly store this connection string in your configuration head over to the ["Secrets" section of the Variables documentation](/docs/environment/variables.mdx#secrets). -Also refer to the [Extensions](/docs/environment/php.md#extensions) section to see if you need to enable any database-specific extensions. +Also refer to the [Extensions](/docs/environment/php.mdx#extensions) section to see if you need to enable any database-specific extensions. ### Learn more diff --git a/docs/environment/logs.md b/docs/environment/logs.mdx similarity index 74% rename from docs/environment/logs.md rename to docs/environment/logs.mdx index cae2783dd..6a433562b 100644 --- a/docs/environment/logs.md +++ b/docs/environment/logs.mdx @@ -1,10 +1,10 @@ ---- -title: Logs -current_menu: logs -introduction: Learn how to write and read PHP logs on AWS Lambda using Bref. ---- +import { NextSeo } from 'next-seo'; -As explained in the [storage documentation](storage.md), the filesystem on AWS Lambda is: + + +# Logs + +As explained in the [storage documentation](./storage.mdx), the filesystem on AWS Lambda is: - read-only, except for `/tmp` - not shared between lambda instances @@ -26,8 +26,8 @@ That means that you don't have to configure anything to log errors, warnings or Your application can write logs to CloudWatch: -- [Bref for web apps](/docs/runtimes/http.md): write logs to `stderr` -- [Bref for event-driven functions](/docs/runtimes/function.md): write logs to `stdout` (using `echo` for example) or `stderr` +- [With the PHP-FPM runtime for web apps](../runtimes/fpm-runtime.mdx): write logs to `stderr` +- [With the runtime for event-driven functions](../runtimes/function.mdx): write logs to `stdout` (using `echo` for example) or `stderr` For example with [Monolog](https://github.com/Seldaek/monolog): diff --git a/docs/environment/performances.md b/docs/environment/performances.mdx similarity index 84% rename from docs/environment/performances.md rename to docs/environment/performances.mdx index c23204e9e..48b1a09ff 100644 --- a/docs/environment/performances.md +++ b/docs/environment/performances.mdx @@ -1,8 +1,9 @@ ---- -title: Performance -current_menu: performances -introduction: Performance tuning and optimizations for serverless PHP applications on AWS Lambda. ---- +import { Callout } from 'nextra/components'; +import { NextSeo } from 'next-seo'; + + + +# Performance This article sums up what to expect in terms of performance and how to optimize serverless PHP applications. The benchmarks included in this page can be reproduced via [the code on GitHub](https://github.com/brefphp/benchmarks). @@ -18,7 +19,7 @@ From 64M to 1,792M, applications run with up to one CPU (1,792M gives 1 full CPU To customize the amount of memory, set the `memorySize` option in `serverless.yml`: -```yaml +```yml filename="serverless.yml" {5} functions: foo: handler: index.php @@ -28,7 +29,7 @@ functions: In the benchmark below, we run [PHP's official `bench.php` script](https://github.com/php/php-src/blob/master/Zend/bench.php). This script is CPU-intensive. -| | 128M | 512M | 1024M | 2048M | +| | 128M | 512M | 1024M | 2048M | |------------------|------:|-----:|------:|------:| | Execution time | 5.7s | 1.4s | 0.65s | 0.33s | @@ -49,11 +50,11 @@ In general, **use smaller and slower lambdas only when speed is not important at ### Bref for web apps -The [FPM runtime for web apps](/docs/runtimes/http.md) **does not add overhead to response times**. +The [FPM runtime for web apps](../runtimes/fpm-runtime.mdx) **does not add overhead to response times**. Here are execution times for an empty PHP application: -| | 128M | 512M | 1024M | 2048M | +| | 128M | 512M | 1024M | 2048M | |------------------|------:|-----:|------:|------:| | Execution time | 10ms | 1ms | 1ms | 1ms | @@ -61,15 +62,15 @@ Unless we use a particularly slow lambda (see the previous section, 128M is not We can see the same result with a "Hello world" written in Symfony (4ms being the minimum execution time of the framework): -| | 128M | 512M | 1024M | 2048M | +| | 128M | 512M | 1024M | 2048M | |------------------|------:|-----:|------:|------:| | Execution time | 58ms | 4ms | 4ms | 4ms | ### Bref for event-driven functions -The [runtime for event-driven functions](/docs/runtimes/function.md) adds a small overhead: +The [runtime for event-driven functions](/docs/runtimes/function.mdx) adds a small overhead: -| | 128M | 512M | 1024M | 2048M | +| | 128M | 512M | 1024M | 2048M | |------------------|------:|-----:|------:|------:| | Execution time | 175ms | 35ms | 16ms | 13ms | @@ -78,7 +79,7 @@ Since this runtime is often used in asynchronous scenarios (for example, process This overhead is caused by the PHP executable starting for every new invocation. We can skip that overhead by keeping the PHP process alive: -```yaml +```yml filename="serverless.yml" {5} functions: hello: # ... @@ -90,7 +91,9 @@ In the example above, the PHP process will restart only every 100 invocations, r In that case, be careful with clearing in-memory data between every event. -> Note: the PHP process will be restarted in case of a failed invocation (PHP exception thrown in the handler). + + Note: the PHP process will be restarted in case of a failed invocation (PHP exception thrown in the handler). + ## Cold starts @@ -104,11 +107,11 @@ On a website with low to medium traffic, you can expect cold starts to happen fo ### Optimizing cold starts -On small websites, cold starts can be avoided by pinging the application regularly. This keeps the lambda instances warm. [Pingdom](https://www.pingdom.com/) or similar services can be used, but you can also [an automatic ping via `serverless.yml`](/docs/runtimes/http.md#cold-starts). +On small websites, cold starts can be avoided by pinging the application regularly. This keeps the lambda instances warm. [Pingdom](https://www.pingdom.com/) or similar services can be used, but you can also [an automatic ping via `serverless.yml`](../use-cases/http/advanced-use-cases#cold-starts). While the memory size has no impact, the codebase size can increase the cold start duration. When deploying, remember to exclude assets, images, tests and any extra file in `serverless.yml`: -```yaml +```yml filename="serverless.yml" package: patterns: - '!assets/**' @@ -117,8 +120,4 @@ package: - ... ``` -Read more about this [in the serverless.yml documentation](serverless-yml.md#exclusions). - -> **A note on VPC cold starts** -> -> Running a function inside a VPC used to induce a cold start of several seconds. This is no longer the case since October 2019. +Read more about this [in the serverless.yml documentation](serverless-yml.mdx#exclusions). diff --git a/docs/environment/php.md b/docs/environment/php.md deleted file mode 100644 index 8256fec21..000000000 --- a/docs/environment/php.md +++ /dev/null @@ -1,176 +0,0 @@ ---- -title: Configuring PHP -current_menu: php -introduction: Configure PHP versions, extensions and options for your serverless application using Bref. ---- - -## php.ini - -PHP will read its configuration from: - -- `/opt/bref/etc/php/php.ini` (PHP's official production configuration) -- `/opt/bref/etc/php/conf.d/bref.ini` (Bref's optimizations for Lambda) - -These files *cannot be customized*. - -### Customizing php.ini - -You can create your own `php.ini` to customize PHP's configuration: - -1. create a `php/conf.d/` subdirectory in your project -1. create a `php.ini` file inside that directory _(the name of the file does not matter, it must have an `.ini` extension)_ - -PHP will automatically include any `*.ini` file found in `php/conf.d/` in your project. - -### Customizing php.ini using a custom path - -If you want PHP to scan a different directory than `php/conf.d/` in your project, you can override the path by setting it in the [`PHP_INI_SCAN_DIR`](http://php.net/manual/configuration.file.php#configuration.file.scan) environment variable. - -> `PHP_INI_SCAN_DIR` must contain an absolute path. Since your code is placed in `/var/task` on AWS Lambda, the environment variable should contain something like `/var/task/my/different/dir`. - -Learn how to declare environment variables by reading the [Environment Variables](variables.md) guide. - -### Customizing php.ini in extra layers - -If you are using Lambda layers, for example to use custom PHP extensions, you can override the default `php.ini` by placing your own configuration file in `/opt/bref/etc/php/conf.d/`. - -Make sure to give a unique name to your `.ini` file to avoid any collision with other layers. - -## Extensions - -Bref strives to include the most common PHP extensions. If a major PHP extension is missing please open an issue to discuss it. - -### Extensions installed and enabled - - - - - - - - - -
- - - - - -
- -### Extensions installed but disabled by default - -- **[intl](http://php.net/manual/en/intro.intl.php)** - Internationalization extension (referred as Intl) is a wrapper for ICU library, enabling PHP programmers to perform various locale-aware operations. -- **[APCu](http://php.net/manual/en/intro.apcu.php)** - APCu is APC stripped of opcode caching. -- **[PostgreSQL PDO Driver](http://php.net/manual/en/ref.pdo-pgsql.php)** - PDO_PGSQL is a driver that implements the PHP Data Objects (PDO) interface to enable access from PHP to PostgreSQL databases. - -You can enable these extensions by loading them in `php/conf.d/php.ini` (as mentioned in [the section above](#phpini)), for example: - -```ini -extension=intl -extension=apcu -extension=pdo_pgsql -``` - -### Extra extensions - -Due to space limitations in AWS Lambda, Bref runtimes cannot include every possible PHP extensions. These additional PHP extensions can be included as separate AWS Lambda layers. - -All extra PHP extensions are found in [brefphp/extra-php-extensions](https://github.com/brefphp/extra-php-extensions). - -Contributions to add more PHP extensions are welcomed. - -### Custom extensions - -It is also possible to provide your own extensions via [custom AWS Lambda layers](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html). - -> This guide is really raw, feel free to contribute to improve it. - -To create your custom layer, you will need to: - -- compile the extension (and any required libraries) in the same environment as AWS Lambda and Bref -- include the compiled extension (and required libraries) in a layer -- upload the layer to AWS Lambda -- include the in your project -- enable the extension in a custom `php.ini` - -To compile the extension, Bref provides the `bref/build-php-*` Docker images. Here is an example with Blackfire: - -```dockerfile -FROM bref/build-php-80:2 - -RUN curl -A "Docker" -o /tmp/blackfire.so -L -s "https://packages.blackfire.io/binaries/blackfire-php/1.42.0/blackfire-php-linux_amd64-php-74.so" - -# Build the final image from the amazon image that is close to the production environment -FROM public.ecr.aws/lambda/provided:al2 - -# Copy things we installed to the final image -COPY --from=0 /tmp/blackfire.so /opt/bref-extra/blackfire.so -``` - -The `.so` extension file can then be retrieved in `/opt/bref-extra/blackfire.so`. -If you installed system libraries, you may also need to copy them to the `public.ecr.aws/lambda/provided:al2` -image. - -See [brefphp/extra-php-extensions](https://github.com/brefphp/extra-php-extensions) -for more examples. - -## Custom vendor path - -Bref automatically requires vendor dependencies from the default `vendor/autoload.php` path. - -If your Composer dependencies are installed elsewhere, you can customize that path via the `BREF_AUTOLOAD_PATH` environment variable. - -```yaml -provider: - # ... - environment: - BREF_AUTOLOAD_PATH: '/var/task/foo-bar/vendor/autoload.php' -``` - -The path must start with `/var/task`, which is the directory where projects are installed on AWS Lambda. diff --git a/docs/environment/php.mdx b/docs/environment/php.mdx new file mode 100644 index 000000000..fabb6b2ad --- /dev/null +++ b/docs/environment/php.mdx @@ -0,0 +1,167 @@ +import { Callout } from 'nextra/components'; +import { NextSeo } from 'next-seo'; + + + +# Configuring PHP + +## php.ini + +PHP will read its configuration from: + +- `/opt/bref/etc/php/php.ini` (PHP's official production configuration) +- `/opt/bref/etc/php/conf.d/bref.ini` (Bref's optimizations for Lambda) + +These files *cannot be customized*. + +### Customizing php.ini + +You can create your own `php.ini` to customize PHP's configuration: + +1. create a `php/conf.d/` subdirectory in your project +1. create a `php.ini` file inside that directory _(the name of the file does not matter, it must have an `.ini` extension)_ + +PHP will automatically include any `*.ini` file found in `php/conf.d/` in your project. + +### Customizing php.ini using a custom path + +If you want PHP to scan a different directory than `php/conf.d/` in your project, you can override the path by setting it in the [`PHP_INI_SCAN_DIR`](https://www.php.net/manual/en/configuration.file.php#configuration.file.scan) environment variable. + + + `PHP_INI_SCAN_DIR` must contain an absolute path. Since your code is placed in `/var/task` on AWS Lambda, the environment variable should contain something like `/var/task/my/different/dir`. + + +Learn how to declare environment variables by reading the [Environment Variables](variables.md) guide. + +### Customizing php.ini in extra layers + +If you are using Lambda layers, for example to use custom PHP extensions, you can override the default `php.ini` by placing your own configuration file in `/opt/bref/etc/php/conf.d/`. + +Make sure to give a unique name to your `.ini` file to avoid any collision with other layers. + +## Extensions + +Bref strives to include the most common PHP extensions. If a major PHP extension is missing please open an issue to discuss it. + +### Built-in extensions + +The following extensions are installed and enabled by default in Bref runtimes: + + + +### Extensions installed but disabled by default + +The following extensions are installed in Bref runtimes, but disabled by default: + +- **[intl](https://www.php.net/manual/en/intro.intl.php)** - Internationalization extension (referred as Intl) is a wrapper for ICU library, enabling PHP programmers to perform various locale-aware operations. +- **[APCu](https://www.php.net/manual/en/intro.apcu.php)** - APCu is APC stripped of opcode caching. +- **[PostgreSQL PDO Driver](https://www.php.net/manual/en/ref.pdo-pgsql.php)** - PDO_PGSQL is a driver that implements the PHP Data Objects (PDO) interface to enable access from PHP to PostgreSQL databases. + +You can enable these extensions by loading them in `php/conf.d/php.ini` (as mentioned in [the section above](#phpini)), for example: + +```ini filename="php/conf.d/php.ini" +extension=intl +extension=apcu +extension=pdo_pgsql +``` + +### Extra extensions + +Due to space limitations in AWS Lambda, Bref runtimes cannot include every possible PHP extensions. These additional PHP extensions can be included as separate AWS Lambda layers. + +All extra PHP extensions are found in [brefphp/extra-php-extensions](https://github.com/brefphp/extra-php-extensions). + +Contributions to add more PHP extensions are welcomed. + +### Custom extensions + +It is also possible to provide your own extensions via [custom AWS Lambda layers](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html). + +> This guide is really raw, feel free to contribute to improve it. + +To create your custom layer, you will need to: + +- compile the extension (and any required libraries) in the same environment as AWS Lambda and Bref +- include the compiled extension (and required libraries) in a layer +- upload the layer to AWS Lambda +- include the in your project +- enable the extension in a custom `php.ini` + +To compile the extension, Bref provides the `bref/build-php-*` Docker images. Here is an example with Blackfire: + +```dockerfile +FROM bref/build-php-80:2 + +RUN curl -A "Docker" -o /tmp/blackfire.so -L -s "https://packages.blackfire.io/binaries/blackfire-php/1.42.0/blackfire-php-linux_amd64-php-74.so" + +# Build the final image from the amazon image that is close to the production environment +FROM public.ecr.aws/lambda/provided:al2 + +# Copy things we installed to the final image +COPY --from=0 /tmp/blackfire.so /opt/bref-extra/blackfire.so +``` + +The `.so` extension file can then be retrieved in `/opt/bref-extra/blackfire.so`. +If you installed system libraries, you may also need to copy them to the `public.ecr.aws/lambda/provided:al2` +image. + +See [brefphp/extra-php-extensions](https://github.com/brefphp/extra-php-extensions) +for more examples. + +## Custom vendor path + +Bref automatically requires vendor dependencies from the default `vendor/autoload.php` path. + +If your Composer dependencies are installed elsewhere, you can customize that path via the `BREF_AUTOLOAD_PATH` environment variable. + +```yml filename="serverless.yml" +provider: + # ... + environment: + BREF_AUTOLOAD_PATH: '/var/task/foo-bar/vendor/autoload.php' +``` + +The path must start with `/var/task`, which is the directory where projects are installed on AWS Lambda. diff --git a/docs/environment/serverless-yml.md b/docs/environment/serverless-yml.mdx similarity index 83% rename from docs/environment/serverless-yml.md rename to docs/environment/serverless-yml.mdx index 6e43f952a..5e24d4061 100644 --- a/docs/environment/serverless-yml.md +++ b/docs/environment/serverless-yml.mdx @@ -1,16 +1,16 @@ ---- -title: serverless.yml -current_menu: serverless-yml -introduction: Configure your application with the serverless.yml file. ---- +import { NextSeo } from 'next-seo'; + + + +# serverless.yml Your application is deployed using the Serverless framework based on the `serverless.yml` configuration file. -This page introduces a few advanced concepts of the `serverless.yml` format. You can learn in the [official Serverless documentation](https://serverless.com/framework/docs/providers/aws/). +This page introduces a few advanced concepts of the `serverless.yml` format. You can learn more in the [official Serverless documentation](https://serverless.com/framework/docs/providers/aws/). ## Overview -```yaml +```yml filename="serverless.yml" service: app provider: @@ -34,24 +34,24 @@ resources: ## Service -```yaml +```yml service: app ``` The [service](https://serverless.com/framework/docs/providers/aws/guide/services/) is simply the name of your project. -Since Serverless lets us deploy a project in [multiple stages](../deploy.md#stages) (prod, dev, staging…), CloudFormation stacks will contain both the service name and the stage: `app-prod`, `app-dev`, etc. +Since Serverless lets us deploy a project in [multiple stages](../deploy.mdx#stages) (prod, dev, staging…), CloudFormation stacks will contain both the service name and the stage: `app-prod`, `app-dev`, etc. ## Provider -```yaml +```yml provider: name: aws ``` Bref only supports the `aws` provider, even though Serverless can deploy applications on other cloud providers like Google Cloud, Azure, etc. -```yaml +```yml provider: name: aws # The AWS region in which to deploy (us-east-1 by default) @@ -199,20 +199,15 @@ resources: # ... ``` -### References - -The CloudFormation `!Ref` syntax can be used. However the `${MyResource.Arn}` CloudFormation syntax cannot be used. +### CloudFormation functions -To solve this, the [serverless-pseudo-parameters plugin](https://github.com/svdgraaf/serverless-pseudo-parameters) can help. After installing it, you can reference other resources by replacing the `${...}` syntax with `#{...}` (because `${...}` is conflicting with [serverless.yml native variables](https://serverless.com/framework/docs/providers/aws/guide/variables/)). +The CloudFormation `!Ref`, `!GetAtt` and `!Sub` functions can be used. -Here is an example where we define a S3 bucket and a policy that references it. It uses both the `!Ref MyBucket` and `#{MyBucket.Arn}` syntaxes: +Here is an example where we define a S3 bucket and a policy that references it. It uses both the `!Ref MyBucket` and `!Sub '${MyBucket.Arn}'` syntaxes: -```yaml +```yml filename="serverless.yml" #... -plugins: - - serverless-pseudo-parameters - resources: Resources: MyBucket: @@ -227,5 +222,5 @@ resources: - Effect: Allow Principal: '*' # everyone Action: s3:GetObject - Resource: '#{MyBucket.Arn}/*' + Resource: !Sub '${MyBucket.Arn}/*' ``` diff --git a/docs/environment/storage.md b/docs/environment/storage.mdx similarity index 92% rename from docs/environment/storage.md rename to docs/environment/storage.mdx index 564366db0..1dd0eaebb 100644 --- a/docs/environment/storage.md +++ b/docs/environment/storage.mdx @@ -1,8 +1,8 @@ ---- -title: Storage -current_menu: storage -introduction: Learn how to store data and files in serverless PHP applications running on AWS Lambda. ---- +import { NextSeo } from 'next-seo'; + + + +# Storage on AWS Lambda Here is a simplified overview of the filesystem on AWS Lambda: @@ -16,7 +16,7 @@ Here is a simplified overview of the filesystem on AWS Lambda: ... ``` -The `/var/task` directory is [limited to 250MB](https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html#function-configuration-deployment-and-execution). If you hit that limit, you can deploy [via Docker images instead](../web-apps/docker.md). +The `/var/task` directory is [limited to 250MB](https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html#function-configuration-deployment-and-execution). If you hit that limit, you can deploy [via Docker images instead](../deploy/docker.mdx). The filesystem on AWS Lambda **is read-only**, except for the `/tmp` directory. @@ -30,13 +30,9 @@ Instead, data can be stored in [databases](/docs/environment/database.md) or in ### S3 storage -It is possible to deploy a S3 bucket in `serverless.yml` using the `Storage` feature of the Lift plugin. For example: - -```yaml -# serverless.yml - -# ... +It is possible to deploy a S3 bucket in `serverless.yml` using the [`Storage` feature of the Lift plugin](https://github.com/getlift/lift/blob/master/docs/storage.md). For example: +```yml filename="serverless.yml" provider: environment: BUCKET_NAME: ${construct:reports-bucket.bucketName} @@ -94,7 +90,7 @@ This solution is ideal for cache data that can change during the life of the app As any service, DynamoDB tables can be deployed via CloudFormation using the `resources` key in `serverless.yml`: -```yaml +```yml filename="serverless.yml" service: app ... @@ -118,13 +114,13 @@ resources: We need to allow code in Lambda functions to access DynamoDB. We can also pass the table name as an environment variable to the application. -```yaml +```yml filename="serverless.yml" service: app provider: iam: role: statements: - - Effect: Allow + - Effect: Allow Resource: !GetAtt CacheTable.Arn Action: - dynamodb:DescribeTable diff --git a/docs/environment/variables.md b/docs/environment/variables.mdx similarity index 88% rename from docs/environment/variables.md rename to docs/environment/variables.mdx index 19ed4d31a..e76c469b2 100644 --- a/docs/environment/variables.md +++ b/docs/environment/variables.mdx @@ -1,8 +1,9 @@ ---- -title: Environment variables -current_menu: variables -introduction: Define environment variables for your Bref application. ---- +import { Callout } from 'nextra/components'; +import { NextSeo } from 'next-seo'; + + + +# Environment variables Environment variables are the perfect solution to configure the application (as recommended in the [12 factor guide](https://12factor.net/config)). @@ -12,7 +13,7 @@ Environment variables can be defined in `serverless.yml`. To define an environment variable that will be available in **all functions** declare it in the `provider` section: -```yaml +```yml filename="serverless.yml" provider: # ... environment: @@ -21,7 +22,7 @@ provider: To define an environment variable that will be available in **a specific function** declare it inside the function's properties: -```yaml +```yml filename="serverless.yml" functions: foo: # ... @@ -29,7 +30,9 @@ functions: MY_VARIABLE: 'my value' ``` -> Do not store secret values in `serverless.yml` directly. Check out the next section to handle secrets. + + Do not store secret values in `serverless.yml` directly. Check out the next section to handle secrets. + ## Secrets @@ -49,7 +52,8 @@ You can also do it in the CLI via the following command: aws ssm put-parameter --region us-east-1 --name '/my-app/my-parameter' --type String --value 'mysecretvalue' ``` -For Windows users, the first part of the path needs to be double slashes and all subsequent forward slashes changed to backslashes: +On Windows, the first part of the path needs to be double slashes and all subsequent forward slashes changed to backslashes: + ```bash aws ssm put-parameter --region us-east-1 --name '//my-app\my-parameter' --type String --value 'mysecretvalue' ``` @@ -67,7 +71,7 @@ You can inject a secret in an environment variable: Use the [`${ssm:}` syntax](https://serverless.com/blog/serverless-secrets-api-keys/) to have the variable be replaced by the secret value on deployment: -```yaml +```yml filename="serverless.yml" provider: # ... environment: @@ -90,13 +94,13 @@ Disadvantages: Alternatively, Bref can fetch the secret values at runtime when the Lambda function starts (aka the "cold start"). To use that feature, we **must install** the `bref/secrets-loader` package: -``` +```bash composer require bref/secrets-loader ``` To use it, the environment variable should contain the path to the SSM parameter prefixed with `bref-ssm:`. We also need to authorize Lambda to retrieve the parameter. For example: -```yaml +```yml filename="serverless.yml" provider: # ... environment: @@ -138,7 +142,7 @@ SSM is good enough for most projects. ## Local development -When [developing locally using `serverless bref:local`](/docs/local-development.md), you can set environment variables using bash: +When [developing locally using `serverless bref:local`](/docs/local-development.mdx), you can set environment variables using bash: ```bash VAR1=val1 VAR2=val2 serverless bref:local -f diff --git a/docs/first-steps.md b/docs/first-steps.md deleted file mode 100644 index 8290229c2..000000000 --- a/docs/first-steps.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: First steps -current_menu: first-steps -introduction: First steps to discover Bref and deploy your first PHP application on AWS Lambda. -previous: - link: /docs/installation.html - title: Installation -next: - link: /docs/runtimes/ - title: What are runtimes? ---- - -This guide will help you deploy your first PHP application on AWS Lambda. For simplicity, we will not be using a PHP framework yet. - -Before getting started make sure you have [installed Bref and the required tools](installation.md) first. - -## Initializing the project - -Starting in an empty directory, install Bref using Composer: - -``` -composer require bref/bref -``` - -> Make sure that the version of Bref that was installed is 1.0 or greater. - -Then let's start by initializing the project by running: - -``` -vendor/bin/bref init -``` - -Accept all the defaults by pressing "Enter". The following files will be created in your project: - -- `index.php` contains the code of your application -- `serverless.yml` contains the configuration for deploying on AWS - -You are free to edit the code in `index.php`, but for now let's keep it simple: we want to run `index.php` on Lambda first. - -## Deployment - -To deploy, let's run: - -```bash -serverless deploy -``` - -Once the command finishes, it should print a URL like this one: - -```sh -https://3pjp2yiw97.execute-api.us-east-1.amazonaws.com -``` - -Open this URL and you should see your application: `index.php` is running on Lambda! - -🎉 congrats on creating your first serverless application! - -To learn more about deployments, head over the [Deployment guide](deploy.md). - -## What's next? - -Now that you have deployed a simple PHP web app, you can [learn more about runtimes](/docs/runtimes/). That will help you deploy HTTP and console applications. diff --git a/docs/frameworks/laravel.md b/docs/frameworks/laravel.md deleted file mode 100644 index df3a30b6a..000000000 --- a/docs/frameworks/laravel.md +++ /dev/null @@ -1,443 +0,0 @@ ---- -title: Serverless Laravel applications -current_menu: laravel -introduction: Learn how to deploy serverless Laravel applications on AWS Lambda using Bref. ---- - -This guide helps you run Laravel applications on AWS Lambda using Bref. These instructions are kept up to date to target the latest Laravel version. - -A demo application is available on GitHub at [github.com/brefphp/examples](https://github.com/brefphp/examples). - -## Setup - -First, make sure you have followed the [Installation guide](../installation.md) to create an AWS account and install the necessary tools. - -Next, in an existing Laravel project, install Bref and the [Laravel-Bref package](https://github.com/brefphp/laravel-bridge). - -``` -composer require bref/bref bref/laravel-bridge --update-with-dependencies -``` - -Then let's create a [`serverless.yml` configuration file](https://bref.sh/docs/environment/serverless-yml.html): - -``` -php artisan vendor:publish --tag=serverless-config -``` - -### How it works - -By default, the Laravel-Bref package will automatically configure Laravel to work on AWS Lambda. - -If you are curious, the package will automatically: - -- enable the `stderr` log driver, to send logs to CloudWatch ([read more about logs](../environment/logs.md)) -- enable the [`cookie` session driver](https://laravel.com/docs/session#configuration) (if you prefer, you can configure sessions to be stored in database, DynamoDB or Redis) -- move the storage directory to `/tmp` (because the default storage directory is read-only on Lambda) -- adjust a few more settings ([have a look at the `BrefServiceProvider` for details](https://github.com/brefphp/laravel-bridge/blob/master/src/BrefServiceProvider.php)) - -## Deployment - -We do not want to deploy "dev" caches that were generated on our machine (because paths will be different on AWS Lambda). Let's clear them before deploying: - -```bash -php artisan config:clear -``` - -When running in AWS Lambda, the Laravel application will automatically cache its configuration when booting. You don't need to run `php artisan config:cache` before deploying. - -Let's deploy now: - -```bash -serverless deploy -``` - -When finished, the `deploy` command will show the URL of the application. - -### Deploying for production - -At the moment, we deployed our local installation to Lambda. When deploying for production, we probably don't want to deploy: - -- development dependencies, -- our local `.env` file, -- or any other dev artifact. - -Follow [the deployment guide](/docs/deploy.md#deploying-for-production) for more details. - -## Troubleshooting - -In case your application is showing a blank page after being deployed, [have a look at the logs](../environment/logs.md). - -## Laravel Artisan - -As you may have noticed, we define a function named "artisan" in `serverless.yml`. That function is using the [Console runtime](/docs/runtimes/console.md), which lets us run Laravel Artisan on AWS Lambda. - -For example, to execute an `artisan` command on Lambda for the above configuration, run the below command. - -```sh -serverless bref:cli --args="" -``` - -For more details follow [the "Console" guide](/docs/runtimes/console.md). - -## Assets - -To deploy Laravel websites, assets need to be served from AWS S3. The easiest approach is to use the [Server-side website construct of the Lift plugin](https://github.com/getlift/lift/blob/master/docs/server-side-website.md). - -This will deploy a Cloudfront distribution that will act as a proxy: it will serve static files directly from S3 and will forward everything else to Lambda. This is very close to how traditional web servers like Apache or Nginx work, which means your application doesn't need to change! For more details, read [the official documentation](https://github.com/getlift/lift/blob/master/docs/server-side-website.md#how-it-works). - -First install the plugin: - -```bash -serverless plugin install -n serverless-lift -``` - -Then add this configuration to your `serverless.yml` file: - -```yaml -service: laravel -provider: - # ... - -functions: - # ... - -plugins: - - ./vendor/bref/bref - - serverless-lift - -constructs: - website: - type: server-side-website - assets: - '/js/*': public/js - '/css/*': public/css - '/favicon.ico': public/favicon.ico - '/robots.txt': public/robots.txt - # add here any file or directory that needs to be served from S3 -``` - -Before deploying, compile your assets: - -```bash -npm run prod -``` - -Now deploy your website using `serverless deploy`. Lift will create all required resources and take care of -uploading your assets to S3 automatically. - -For more details, see the [Websites section](/docs/websites.md) of this documentation and the official [Lift documentation](https://github.com/getlift/lift/blob/master/docs/server-side-website.md). - -### Assets in templates - -Assets referenced in templates should be via the `asset()` helper: - -```html - -``` - -If your templates reference some assets via direct path, you should edit them to use the `asset()` helper: - -```html -- -+ -``` - -## File storage on S3 - -Laravel has a [filesystem abstraction](https://laravel.com/docs/filesystem) that lets us easily change where files are stored. When running on Lambda, you will need to use the `s3` adapter to store files on AWS S3. - -To do this, set `FILESYSTEM_DISK: s3` either in `serverless.yml` or your production `.env` file. We can also create an S3 bucket via `serverless.yml` directly: - -```yaml -# ... -provider: - # ... - environment: - # environment variable for Laravel - FILESYSTEM_DISK: s3 - AWS_BUCKET: !Ref Storage - iam: - role: - statements: - # Allow Lambda to read and write files in the S3 buckets - - Effect: Allow - Action: s3:* - Resource: - - !Sub '${Storage.Arn}' # the storage bucket - - !Sub '${Storage.Arn}/*' # and everything inside - -resources: - Resources: - # Create our S3 storage bucket using CloudFormation - Storage: - Type: AWS::S3::Bucket -``` - -That's it! The AWS credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN) are set automatically in AWS Lambda, you don't have to define them. - -### Public files - -Laravel has a [special disk called `public`](https://laravel.com/docs/filesystem#the-public-disk): this disk stores files that we want to make public, like uploaded photos, generated PDF files, etc. - -Again, those files cannot be stored on Lambda, i.e. they cannot be stored in the default `storage/app/public` directory. You need to store those files on S3. - -> Do not run `php artisan storage:link` in AWS Lambda: it is now useless, and it will fail because the filesystem is read-only in Lambda. - -To store public files on S3, you could replace the disk in the code: - -```diff -- Storage::disk('public')->put('avatars/1', $fileContents); -+ Storage::disk('s3')->put('avatars/1', $fileContents); -``` - -but doing this will not let your application work locally. A better solution, but more complex, involves making the `public` disk configurable. Let's change the configuration in `config/filesystems.php`: - -```diff - /* - |-------------------------------------------------------------------------- - | Default Public Filesystem Disk - |-------------------------------------------------------------------------- - */ - -+ 'public' => env('FILESYSTEM_DISK', 'public_local'), - - ... - - 'disks' => [ - - 'local' => [ - 'driver' => 'local', - 'root' => storage_path('app'), - ], - -- 'public' => [ -+ 'public_local' => [ - 'driver' => 'local', - 'root' => storage_path('app/public'), - 'url' => env('APP_URL').'/storage', - 'visibility' => 'public', - ], - - 's3' => [ - 'driver' => 's3', - 'key' => env('AWS_ACCESS_KEY_ID'), - 'secret' => env('AWS_SECRET_ACCESS_KEY'), - 'token' => env('AWS_SESSION_TOKEN'), - 'region' => env('AWS_DEFAULT_REGION'), - 'bucket' => env('AWS_BUCKET'), - 'url' => env('AWS_URL'), - ], - -+ 's3_public' => [ -+ 'driver' => 's3', -+ 'key' => env('AWS_ACCESS_KEY_ID'), -+ 'secret' => env('AWS_SECRET_ACCESS_KEY'), -+ 'token' => env('AWS_SESSION_TOKEN'), -+ 'region' => env('AWS_DEFAULT_REGION'), -+ 'bucket' => env('AWS_PUBLIC_BUCKET'), -+ 'url' => env('AWS_URL'), -+ ], - - ], -``` - -You can now configure the `public` disk to use S3 by changing `serverless.yml` or your production `.env`: - -```dotenv -FILESYSTEM_DISK=s3 -FILESYSTEM_DISK_PUBLIC=s3 -``` - -## Laravel Queues - -To run Laravel Queues on AWS Lambda using [Amazon SQS](https://aws.amazon.com/sqs/), we don't want to run the `php artisan queue:work` command. Instead, we create a function that is invoked immediately when there are new jobs to process. - -To create the SQS queue (and the permissions for the Lambda functions to read/write to it), we can either do that manually, or use `serverless.yml`. - -To make things simpler, we will use the [Serverless Lift](https://github.com/getlift/lift) plugin to create and configure the SQS queue. - -First install the Lift plugin: - -```bash -serverless plugin install -n serverless-lift -``` - -Then use the Queue construct in `serverless.yml`: - -```yml -provider: - # ... - environment: - # ... - QUEUE_CONNECTION: sqs - SQS_QUEUE: ${construct:jobs.queueUrl} - -functions: - # ... - -constructs: - jobs: - type: queue - worker: - handler: Bref\LaravelBridge\Queue\QueueHandler - runtime: php-81 - timeout: 60 # seconds -``` - -We define Laravel environment variables in `provider.environment` (this could also be done in the deployed `.env` file): - -- `QUEUE_CONNECTION: sqs` enables the SQS queue connection -- `SQS_QUEUE: ${construct:jobs.queueUrl}` passes the URL of the created SQS queue - -If you want to create the SQS queue manually, you will need to set these variables. AWS credentials (`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`) are automatically set up with the appropriate permissions for Laravel to use the SQS queue. - -That's it! Anytime a job is pushed to Laravel Queues, it will be sent to SQS, and SQS will invoke our "worker" function so that it is processed. - -> **Note**: -> -> In the example above, we set the full SQS queue URL in the `SQS_QUEUE` variable. -> -If you only set the queue name (which is also valid), you need to set the `SQS_PREFIX` environment variable too. For example: `SQS_PREFIX: "https://sqs.${aws:region}.amazonaws.com/${aws:accountId}"`. - -### How it works - -When integrated with AWS Lambda, SQS has a built-in retry mechanism and storage for failed messages. These features work slightly differently than Laravel Queues. The "Bref for Laravel" integration does **not** use these SQS features. - -Instead, "Bref for Laravel" makes all the feature of Laravel Queues work out of the box, just like on any server. Read more in [the Laravel Queues documentation](https://laravel.com/docs/latest/queues). - -> **Note:** the "Bref-Laravel bridge" v1 used to do the opposite. We changed that behavior in Bref v2 in order to make the experience smoother for Laravel users. - -## Laravel Octane - -To run the HTTP application with [Laravel Octane](https://laravel.com/docs/10.x/octane) instead of PHP-FPM, change the following options in the `web` function: - -```yml -functions: - web: - handler: Bref\LaravelBridge\Http\OctaneHandler - runtime: php-81 - environment: - BREF_LOOP_MAX: 250 - # ... -``` - -Keep the following details in mind: - -- Laravel Octane does not need Swoole or RoadRunner on AWS Lambda, so it is not possible to use Swoole-specific features. -- Octane keeps Laravel booted in a long-running process, [beware of memory leaks](https://laravel.com/docs/10.x/octane#managing-memory-leaks). -- `BREF_LOOP_MAX` specifies the number of HTTP requests handled before the PHP process is restarted (and the memory is cleared). - -### Persistent database connections - -You can keep database connections persistent across requests to make your application even faster. To do so, set the `OCTANE_PERSIST_DATABASE_SESSIONS` environment variable: - -```yml -functions: - web: - handler: Bref\LaravelBridge\Http\OctaneHandler - runtime: php-81 - environment: - BREF_LOOP_MAX: 250 - OCTANE_PERSIST_DATABASE_SESSIONS: 1 - # ... -``` - -Note that if you are using PostgreSQL (9.6 or newer), you need to set [`idle_in_transaction_session_timeout`](https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-IDLE-IN-TRANSACTION-SESSION-TIMEOUT) either in your RDS database's parameter group, or on a specific database itself. - -```sql -ALTER DATABASE SET idle_in_transaction_session_timeout = '10000' -- 10 seconds in ms -``` - -## Caching - -By default, the Bref bridge will move Laravel's storage and cache directories to `/tmp`. This is because all the filesystem except `/tmp` is read-only. - -Note that the `/tmp` directory isn't shared across Lambda instances. If you Lambda function scales up, the cache will be empty in new instances (or after a deployment). - -If you want the cache to be shared across all Lambda instances, for example if your application caches a lot of data or if you use it for locking mechanisms (like API rate limiting), you can instead use Redis or DynamoDB. - -DynamoDB is the easiest to set up and is "pay per use". Redis is a bit more complex as it requires a VPC and managing instances, but offers slightly faster response times. - -### Using DynamoDB - -To use DynamoDB as a cache store, change this configuration in `config/cache.php`: - -```diff - # config/cache.php - 'dynamodb' => [ - 'driver' => 'dynamodb', - 'key' => env('AWS_ACCESS_KEY_ID'), - 'secret' => env('AWS_SECRET_ACCESS_KEY'), - 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), - 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), - 'endpoint' => env('DYNAMODB_ENDPOINT'), -+ 'attributes' => [ -+ 'key' => 'id', -+ 'expiration' => 'ttl', -+ ] - ], -``` - -Then follow [this section of the documentation](/docs/environment/storage.md#deploying-dynamodb-tables) to deploy your DynamoDB table using the Serverless Framework. - -## Maintenance mode - -Similar to the `php artisan down` command, you may put your app into maintenance mode. All that's required is setting the `MAINTENANCE_MODE` environment variable: - -```yml -provider: - environment: - MAINTENANCE_MODE: ${param:maintenance, null} -``` - -You can then deploy: - -```bash -# Full deployment (goes through CloudFormation): -serverless deploy --param="maintenance=1" - -# Or quick update of the functions config only: -serverless deploy function --function=web --update-config --param="maintenance=1" -serverless deploy function --function=artisan --update-config --param="maintenance=1" -serverless deploy function --function= --update-config --param="maintenance=1" -``` - -To take your app out of maintenance mode, redeploy without the `--param="maintenance=1"` option. - -## Laravel Passport - -Laravel Passport has a `passport:install` command. However, this command cannot be run in Lambda because it needs to write files to the `storage/` directory. - -Instead, here is what you need to do: - -- Run `php artisan passport:keys` locally to generate key files. - - This command will generate the `storage/oauth-private.key` and `storage/oauth-public.key` files, which need to be deployed. - - Depending on how you deploy your application (from your machine, or from CI), you may want to whitelist them in `serverless.yml`: - - ```yaml - package: - patterns: - - ... - # Exclude the 'storage' directory - - '!storage/**' - # Except the public and private keys required by Laravel Passport - - 'storage/oauth-private.key' - - 'storage/oauth-public.key' - ``` - -- You can now deploy the application: - - ```yaml - serverless deploy - ``` - -- Finally, you can create the tokens (which is the second part of the `passport:install` command): - - ```bash - serverless bref:cli --args="passport:client --personal --name 'Laravel Personal Access Client'" - serverless bref:cli --args="passport:client --password --name 'Laravel Personal Access Client'" - ``` - -All these steps were replacements of running the `passport:install` command [from the Passport documentation](https://laravel.com/docs/passport#installation). diff --git a/docs/frameworks/symfony.md b/docs/frameworks/symfony.md deleted file mode 100644 index 07dd65386..000000000 --- a/docs/frameworks/symfony.md +++ /dev/null @@ -1,247 +0,0 @@ ---- -title: Serverless Symfony applications -current_menu: symfony -introduction: Learn how to deploy serverless Symfony applications on AWS Lambda using Bref. ---- - -This guide helps you run Symfony applications on AWS Lambda using Bref. These instructions are kept up-to-date to be compatible with the latest Symfony version. - -Multiple demo applications are available on GitHub at [github.com/brefphp/examples/Symfony](https://github.com/brefphp/examples/tree/master/Symfony). - -## Setup - -First, **follow the [Installation guide](../installation.md)** to create an AWS account and install the necessary tools. - -Next, in an existing Symfony project, install Bref and the [Symfony Bridge package](https://github.com/brefphp/symfony-bridge). - -``` -composer require bref/bref bref/symfony-bridge -``` - -If you are using [Symfony Flex](https://flex.symfony.com/), it will automatically run -the [bref/symfony-bridge recipe](https://github.com/symfony/recipes-contrib/tree/master/bref/symfony-bridge/0.1) which will perform the following tasks: - -- Create a `serverless.yml` configuration file optimized for Symfony. -- Add the `.serverless` folder to the `.gitignore` file. - -> Otherwise, you can create the `serverless.yml` file manually at the root of the project. Take a look -at the [default configuration](https://github.com/symfony/recipes-contrib/blob/master/bref/symfony-bridge/0.1/serverless.yaml) provided by the recipe. - -You still have a few modifications to do on the application to make it compatible with AWS Lambda. - -Since [the filesystem is readonly](/docs/environment/storage.md) except for `/tmp` we need to customize where the cache and logs are stored in -the `src/Kernel.php` file. This is automatically done by the bridge, you just need to use the `BrefKernel` class instead of the default `BaseKernel`: - -```diff -// src/Kernel.php - -namespace App; - -+ use Bref\SymfonyBridge\BrefKernel; -use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; -use Symfony\Component\Config\Loader\LoaderInterface; -use Symfony\Component\Config\Resource\FileResource; -use Symfony\Component\DependencyInjection\ContainerBuilder; --use Symfony\Component\HttpKernel\Kernel as BaseKernel; -use Symfony\Component\Routing\RouteCollectionBuilder; - -- class Kernel extends BaseKernel -+ class Kernel extends BrefKernel -{ - // ... -``` - -## Deploy - -The application is now ready to be deployed. Follow [the deployment guide](/docs/deploy.md). - -For better performance in production, warmup the Symfony cache before deploying: - -```bash -php bin/console cache:warmup --env=prod -``` - -## Console - -As you may have noticed, we define a function of type "console" in `serverless.yml`. That function is using the [Console runtime](/docs/runtimes/console.md), -which lets us run the Symfony Console on AWS Lambda. - -To use it follow [the "Console" guide](/docs/runtimes/console.md). - -## Logs - -By default, Symfony logs in `stderr`. That is great because Bref [automatically forwards `stderr` to AWS CloudWatch](/docs/environment/logs.md). - -However, if the application is using Monolog you need to configure it to log into `stderr` as well: - -```yaml -# config/packages/prod/monolog.yaml - -monolog: - handlers: - # ... - nested: - type: stream - path: php://stderr -``` - -## Environment variables - -Since Symfony 4, the production parameters are configured through environment variables. You can define them in `serverless.yml`. - -```yaml -# serverless.yml - -provider: - environment: - APP_ENV: prod -``` - -The secrets (e.g. database passwords) must however not be committed in this file. - -To learn more about all this, read the [environment variables documentation](/docs/environment/variables.md). - -## Trust API Gateway - -When hosting your site on Lambda, API Gateway will act as a proxy between the client and your function. - -By default, Symfony doesn't trust proxies for security reasons, but it's safe to do it when using API Gateway and Lambda. - -This is needed because otherwise, Symfony will not be able to generate URLs properly. - -You should add the following lines to `config/packages/framework.yaml` - -```yaml -# config/packages/framework.yaml - -framework: - # trust the remote address because API Gateway has no fixed IP or CIDR range that we can target - trusted_proxies: '127.0.0.1' - # trust "X-Forwarded-*" headers coming from API Gateway - trusted_headers: [ 'x-forwarded-for', 'x-forwarded-proto', 'x-forwarded-port' ] -``` - -Note that API Gateway doesn't set the `X-Forwarded-Host` header, so we don't trust it by default. You should only whitelist this header if you set it manually, -for example in your CloudFront configuration (this is done automatically -in [the Cloudfront distribution deployed by Lift](#assets)). - -> Be careful with these settings if your app will not be executed only in a Lambda environment. - -You can get more details in the [Symfony documentation](https://symfony.com/doc/current/deployment/proxies.html). - -### Getting the user IP - -**When using CloudFront** on top of API Gateway, you will not be able to retrieve the client IP address, and you will instead get one of Cloudfront's IP when -calling `Request::getClientIp()`. If you really need this, you will need to -whitelist [every CloudFront IP](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/LocationsOfEdgeServers.html) -in `trusted_proxies`. - -## Assets - -To deploy Symfony websites, assets need to be served from AWS S3. The easiest approach is to use the -Server-side website construct of the Lift plugin. - -This will deploy a Cloudfront distribution that will act as a proxy: it will serve -static files directly from S3 and will forward everything else to Lambda. This is very close -to how traditional web servers like Apache or Nginx work, which means your application doesn't need to change! -For more details, see the official documentation. - -First install the plugin - -```bash -serverless plugin install -n serverless-lift -``` - -Then add this configuration to your `serverless.yml` file. - -```yaml -... -service: symfony - -provider: - ... - -plugins: - - ./vendor/bref/bref - - serverless-lift - -functions: - ... - -constructs: - website: - type: server-side-website - assets: - '/bundles/*': public/bundles - '/build/*': public/build - '/favicon.ico': public/favicon.ico - '/robots.txt': public/robots.txt - # add here any file or directory that needs to be served from S3 -``` - -Because this construct sets the `X-Forwarded-Host` header by default, you should add it in your `trusted_headers` config, otherwise Symfony -might generate wrong URLs. - -```diff -# config/packages/framework.yaml - -- trusted_headers: [ 'x-forwarded-for', 'x-forwarded-proto', 'x-forwarded-port' ] -+ trusted_headers: [ 'x-forwarded-for', 'x-forwarded-proto', 'x-forwarded-port', 'x-forwarded-host' ] -``` - -Then, you can compile assets for production in the `public` directory - -```bash -php bin/console assets:install --env prod -# if using Webpack Encore, additionally run -yarn encore production -``` - -Now run `serverless deploy`, Lift will automatically create the S3 bucket, a Cloudfront distribution and -upload all specified files and directories to the bucket. - -> If you are not using Flex, update the `serverless.yml` file to exclude assets from the deployment ([see the recipe](https://github.com/symfony/recipes-contrib/blob/master/bref/symfony-bridge/0.1/serverless.yaml#L35)) - -For more details, see the [Websites section](/docs/websites.md) of this documentation -and the official Lift documentation. - -### Assets in templates - -For the above configuration to work, assets must be referenced in templates via the `asset()` helper as [recommended by Symfony](https://symfony.com/doc/current/templates.html#linking-to-css-javascript-and-image-assets): - -```diff -- -+ -``` - -## Symfony Messenger - -It is possible to run Symfony Messenger workers on AWS Lambda. - -A dedicated Bref package is available for this: [bref/symfony-messenger](https://github.com/brefphp/symfony-messenger). - -## Caching - -As mentioned above the filesystem is readonly, so if you need a persistent cache it must be stored somewhere else (such as Redis, an RDBMS, or DynamoDB). - -### Using DynamoDB for cache - -A Symfony bundle is available to use AWS DynamoDB as cache store: [rikudou/psr6-dynamo-db-bundle](https://github.com/RikudouSage/DynamoDbCachePsr6Bundle) - -First install the bundle - -```bash -composer require rikudou/psr6-dynamo-db-bundle -``` - -Thanks to Symfony Flex, the bundle comes pre-configured to run in Lambda. - -Now, you can follow [this section of the documentation](/docs/environment/storage.md#deploying-dynamodb-tables) to deploy your DynamoDB table using the Serverless Framework. - -## The `kernel.terminate` Event - -The [`kernel.terminate` event](https://symfony.com/doc/current/components/http_kernel.html#component-http-kernel-kernel-terminate) runs **synchronously** on Lambda. - -That means that if you use this event, its listeners will be executed **before** the Lambda function returns its response. That will add latency to your response. - -To run asynchronous tasks, use the [Messenger component](#symfony-messenger) instead. diff --git a/docs/function/cron.md b/docs/function/cron.md deleted file mode 100644 index 8404b3118..000000000 --- a/docs/function/cron.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: Cron functions on AWS Lambda -current_menu: cron-function -introduction: Learn how to create serverless cron functions with PHP on AWS Lambda. -previous: - link: /docs/function/local-development.html - title: Local development ---- - -AWS Lambda lets us run [PHP functions](/docs/runtimes/function.md) as cron tasks using the `schedule` event: - -```yaml -functions: - console: - handler: function.php - runtime: php-81 - events: - - schedule: rate(1 hour) -``` - -The example above will run the function returned by `function.php` every hour in AWS Lambda. For example: - -```php - If you are interested in **cron CLI commands** instead, read the [Cron commands](/docs/web-apps/cron.html) documentation. diff --git a/docs/function/handlers.md b/docs/function/handlers.md deleted file mode 100644 index 891c4168e..000000000 --- a/docs/function/handlers.md +++ /dev/null @@ -1,458 +0,0 @@ ---- -title: Typed PHP Lambda handlers -current_menu: typed-handlers -introduction: Handle AWS Lambda events using typed PHP classes. -previous: - link: /docs/runtimes/function.html - title: PHP functions on AWS Lambda -next: - link: /docs/function/local-development.html - title: Local development ---- - -Handling Lambda events via an anonymous function is the simplest approach: - -```php -return function ($event) { - return 'Hello ' . $event['name']; -}; -``` - -However, Bref also provides classes specific to each Lambda event for a better development experience. - -Here is an example using the base `Handler` class, that can handle events of any type: - -```php -getRecords()[0]->getBucket()->getName(); - $fileName = $event->getRecords()[0]->getObject()->getKey(); - - // do something with the file - } -} - -return new Handler(); -``` - -For example, the class can be called whenever a new file is uploaded to S3: - -```yaml -# ... - -functions: - resizeImage: - handler: handler.php - events: - - s3: photos -``` - -[Full reference of S3 in `serverless.yml`](https://www.serverless.com/framework/docs/providers/aws/events/s3/). - -## SQS events - -`SqsHandler` instances handle [SQS events](https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html): - -```php -getRecords() as $record) { - // We can retrieve the message body of each record via `->getBody()` - $body = $record->getBody(); - - // do something - } - } -} - -return new Handler(); -``` - -### Partial Batch Response - -While handling a batch of records, you can mark it as partially successful to reprocess only the failed records. - -In your function declaration in `serverless.yml`, set `functionResponseType` to `ReportBatchItemFailures` to let your function return a partial success result if one or more messages in the batch have failed. - -```yaml -functions: - worker: - handler: handler.php - events: - - sqs: - arn: arn:aws:sqs:eu-west-1:111111111111:queue-name - batchSize: 100 - functionResponseType: ReportBatchItemFailures -``` - -In your PHP code, you can now use the `markAsFailed` method: - -```php - public function handleSqs(SqsEvent $event, Context $context): void - { - foreach ($event->getRecords() as $record) { - // do something - - // if something went wrong, mark the record as failed - $this->markAsFailed($record); - } - } -``` - -### Lift Queue Construct - -It is possible to deploy a preconfigured SQS queue in `serverless.yml` using the `Queue` feature of the Lift plugin. For example: - -```yaml -# serverless.yml -# ... - -constructs: - my-queue: - type: queue - worker: - handler: handler.php -``` - -Read more: - -- Deploying SQS queues with Lift -- [Full reference of SQS in `serverless.yml`](https://www.serverless.com/framework/docs/providers/aws/events/sqs/) -- Learn more about SQS and workers in [Serverless Visually Explained](https://serverless-visually-explained.com/) - -## API Gateway HTTP events - -**Reminder:** to create HTTP applications, it is possible to use the more traditional "[Bref for web apps](/docs/runtimes/http.md)" runtime, which runs with PHP-FPM. - -That being said, it is possible to handle HTTP events from API Gateway with a simple PHP class, like other handlers detailed in this page. - -Here is a full comparison between both approaches: - -| | Bref for web apps | HTTP handler class | -|----------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------| -| What are the use cases? | To build websites, APIs, etc. This should be the **default approach** as it's compatible with mature PHP frameworks and tools. | Build a small website, API, webhook with very little code and no framework. | -| Why does that solution exist? | For out-of-the-box compatibility with frameworks like Symfony and Laravel. | To match how other languages run in AWS Lambda, as recommended by AWS. | -| How is it executed? | Using PHP-FPM. | Using the PHP CLI. | -| What does the routing (i.e. separate pages)? | Your PHP framework (one Lambda receives all the URLs). | API Gateway: we define one Lambda and one handler class per route. | -| How to read the request? | `$_GET`, `$_POST`, etc. | The `$request` parameter (PSR-7 request). | -| How to write a response? | `echo`, `header()` function, etc. | Returning a PSR-7 response from the handler class. | -| How does it work? | Bref turns an API Gateway event into a FastCGI (PHP-FPM) request. | Bref turns an API Gateway event into a PSR-7 request. | -| Is each request handled in a separate PHP process? | Yes (that's how PHP-FPM works). | Yes (Bref explicitly replicates that to avoid surprises, but that can be customized). | - -To create an HTTP handler class, Bref natively supports the [PSR-15](https://www.php-fig.org/psr/psr-15/#2-interfaces) and [PSR-7](https://www.php-fig.org/psr/psr-7/) standards: - -```php -getQueryParams()['name'] ?? 'world'; - - return new Response(200, [], "Hello $name"); - } -} - -return new HttpHandler(); -``` - -Since a handler is a controller for a specific route, we can use API Gateway's routing to deploy multiple Lambda functions: - -```yaml -functions: - create-article: - handler: create-article-handler.php - runtime: php-81 - events: - - httpApi: 'POST /articles' - get-article: - handler: get-article-handler.php - runtime: php-81 - events: - - httpApi: 'GET /articles/{id}' -``` - -Note that path parameters (e.g. `{id}` in the example above) are available as request attributes in the PSR-7 request: - -```php -$id = $request->getAttribute('id'); -``` - -[Full reference of HTTP events in `serverless.yml`](https://www.serverless.com/framework/docs/providers/aws/events/http-api/). - -### Lambda event and context - -The API Gateway event and Lambda context are available as attributes on the request: -```php -/** @var $event Bref\Event\Http\HttpRequestEvent */ -$event = $request->getAttribute('lambda-event'); - -/** @var $context Bref\Context\Context */ -$context = $request->getAttribute('lambda-context'); -``` - -If you're looking for the request context array, for example when using a [Lambda authorizer](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html#http-api-lambda-authorizer.payload-format-response): -```php -$requestContext = $request->getAttribute('lambda-event')->getRequestContext(); -``` - -## Websocket events - -`WebsocketHandler` instances handle Websocket events: - -```php -getRouteKey(); - $eventType = $event->getEventType(); - $body = $event->getBody(); - - return new HttpResponse('ok'); - } -} - -return new Handler(); -``` - -[Full reference for Websockets in `serverless.yml`](https://www.serverless.com/framework/docs/providers/aws/events/websocket/). - -A complete WebSocket example is available in [Serverless Visually Explained](https://serverless-visually-explained.com/). - -## EventBridge events - -`EventBridgeHandler` instances handle EventBridge events: - -```php -getDetail()` - $message = $event->getDetail(); - - // do something - } -} - -return new Handler(); -``` - -You can read more about messaging with EventBridge in [Serverless Visually Explained](https://serverless-visually-explained.com/). - -[Full reference of EventBridge in `serverless.yml`](https://www.serverless.com/framework/docs/providers/aws/events/event-bridge/). - -## SNS events - -`SnsHandler` instances handle [SNS events](https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html): - -```php -getRecords() as $record) { - $message = $record->getMessage(); - - // do something - } - } -} - -return new Handler(); -``` - -[Full reference of SNS in `serverless.yml`](https://www.serverless.com/framework/docs/providers/aws/events/sns/). - -## DynamoDB events - -`DynamoDbHandler` instances handle [DynamoDB events](https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html): - -```php -getRecords() as $record) { - $keys = $record->getKeys(); - $old = $record->getOldImage(); - $new = $record->getNewImage(); - - // do something - } - } -} - -return new Handler(); -``` - -[Full reference of DynamoDB in `serverless.yml`](https://www.serverless.com/framework/docs/providers/aws/events/streams/). - -## Kinesis events - -`KinesisHandler` instances handle [Kinesis events](https://docs.aws.amazon.com/lambda/latest/dg/with-kinesis.html): - -```php -getRecords() as $record) { - $data = $record->getData(); - - // do something - } - } -} - -return new Handler(); -``` - -[Full reference of Kinesis in `serverless.yml`](https://www.serverless.com/framework/docs/providers/aws/events/streams/). - -## Kafka events - -`KafkaHandler` instances handle [Kafka events](https://docs.aws.amazon.com/lambda/latest/dg/with-kafka.html): - -```php -getRecords() as $record) { - $data = $record->getValue(); - - // do something - } - } -} - -return new Handler(); -``` diff --git a/docs/index.mdx b/docs/index.mdx new file mode 100644 index 000000000..a18722df4 --- /dev/null +++ b/docs/index.mdx @@ -0,0 +1,220 @@ +import { NextSeo } from 'next-seo'; + + + +# What is Bref and serverless? + +Serverless means using cloud services that manage the servers for us. + +## Why serverless? + +When running PHP on a server, we must: + +- setup, configure and maintain that server, +- pay a fixed price for the server, +- scale the server(s) if we get more traffic. + +When running PHP serverless: + +- We do not need to set up servers, the cloud provider takes care of that. +- We pay only for what we use (per request). +- Our application scales automatically. + +**Serverless provides more scalable, affordable and reliable architectures for less effort.** + +Serverless includes services like storage as a service, database as a service, message queue as a service, etc. One service in particular is interesting for us developers: *Function as a Service* (FaaS). + +FaaS is a way to run code where the hosting provider takes care of setting up everything, keeping the application available 24/7, scaling it up and down and we are only charged *while the code is actually executing*. + +## Why Bref? + +Bref aims to make running PHP applications simple. + +To reach that goal, Bref takes advantage of serverless technologies. However, while serverless is promising, there are many choices to make, tools to build and best practices to figure out. + +Bref's approach is to: + +- **simplify problems by removing choices** + + *instead of trying to address every need* +- **provide simple and familiar solutions** + + *instead of aiming for powerful custom solutions* +- **empower by sharing knowledge** + + *instead of hiding too much behind leaky abstractions* + +### What is Bref + +Bref (which means "brief" in french) comes as an open source Composer package and helps you deploy PHP applications to [AWS](https://aws.amazon.com) and run them on [AWS Lambda](https://aws.amazon.com/lambda/). + +Bref provides: + +- documentation +- PHP runtimes for AWS Lambda +- deployment tooling +- PHP frameworks integration + +The choice of AWS as serverless provider is deliberate: at the moment AWS is the leading hosting provider, it is ahead in the serverless space in terms of features, performance and reliability. + +Bref uses [the Serverless framework](https://serverless.com/) to configure and deploy serverless applications. Being the most popular tool, Serverless comes with a huge community, a lot of examples online and a simple configuration format. + +## Use cases + +Bref and AWS Lambda can be used to run many kind of PHP application, for example: + +- APIs +- websites +- workers +- batch processes/scripts +- event-driven microservices + +Bref aims to support any PHP framework. It comes with deep integrations with Laravel and Symfony. + +If you are interested in real-world examples as well as cost analyses head over to the [**Case Studies** page](/docs/case-studies.md). + +## Maturity matrix + +The matrix below provides an overview of the "maturity level" for common PHP applications. + +This maturity level is a vague metric, however it can be useful to anticipate the effort and the limitations to expect for each scenario. While a green note doesn't mean that Bref and Lambda are silver bullets for the use case (there are no silver bullets), a red note doesn't mean this is impossible or advised against. + +This matrix will be updated as Bref and AWS services evolve over time. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SimplicityPerformanceReliability
+ Jobs, Cron + + + + + + +
API + + + + + +
Website + + + + + +
Legacy application + + + + + +
Event-driven microservices + + + + + +
Real-time applications + + + + + +
+ Is this documented and simple to achieve? + + Is performance acceptable? + + Is this scenario production-ready? +
+ +
+ Legend: + Good use case + Some drawbacks + Strong limitations +
+ +- **Jobs, Cron** + + Jobs, cron tasks and batch processes are very good candidates for FaaS. The scaling model of AWS Lambda can lead to very high throughput in queue processing, and the pay-per-use billing model can sometimes result in drastic costs reduction. + + Using Bref, it is possible to implement cron jobs and queue workers using PHP. Bref also provides integration with popular queue libraries, like Laravel Queues and Symfony Messenger. + + One limitation to keep in mind is that each AWS Lambda invocation has a maximum execution time of 15 minutes. + +- **API** + + APIs run on AWS Lambda without problems. Performance is now similar to what you could expect on a traditional VPS. + + The main difference to account for is that about 0.5% of HTTP requests are cold starts. If your use case requires that _all_ requests are handled below 10ms, serverless might not be a good fit. + +- **Website** + + Websites run well on AWS Lambda. Assets can be stored in S3 and served via Cloudfront. This is documented in the ["Websites" documentation](/docs/use-cases/websites.mdx). Performance is as good as any server. + +- **Legacy application** + + Migrating a legacy PHP application to Bref and Lambda can be a challenge. One could expect to rewrite some parts of the code to make the application fit for Lambda (or running in containers in general). For example, file uploads and sessions often need to be adapted to work with the read-only filesystem. Cron tasks, scripts or asynchronous jobs must be made compatible with Lambda and SQS. + + Not impossible, but definitely not the easiest place to start. As a first step, you can follow the guidelines of [The Twelve-Factor App](https://12factor.net). Note that if your application already runs redundantly on multiple servers, it is much more ready for AWS Lambda and the migration could be simple. + +- **Real-time applications** + + Warm Lambda invocations are very fast (can be as low as 1ms), but cold starts can take 230ms or more. Cold starts are rare on most applications (less than 0.5% of invocations) and can be further mitigated with [provisioned concurrency](https://docs.aws.amazon.com/lambda/latest/dg/provisioned-concurrency.html), but it's unlikely to ensure they will _never_ happen. This makes Lambda a poor choice for real-time applications where latency must be below 100ms for 100% of requests. + +## Getting started + +Get started with Bref by reading the [installation documentation](/docs/setup.mdx). diff --git a/docs/installation.md b/docs/installation.md deleted file mode 100644 index 4c6d02096..000000000 --- a/docs/installation.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: Installation -current_menu: installation -introduction: How to install Bref and the required tools. -previous: - link: /docs/ - title: What is Bref and serverless? -next: - link: /docs/first-steps.html - title: First steps ---- - -To set up Bref correctly please complete all the sections below. - -## AWS account - -You will need an AWS account. To create one, go to [aws.amazon.com](https://aws.amazon.com/) and click *Sign up*. - -AWS has a generous free tier that will usually allow you to deploy your first test applications for free. - -## Serverless - -Bref relies on the [Serverless framework](https://serverless.com/) and AWS access keys to deploy applications. You will need to: - -- install the `serverless` command ([more details here](https://serverless.com/framework/docs/providers/aws/guide/quick-start/)): - - ```bash - npm install -g serverless - ``` - - _Bref is compatible with Serverless Framework v3 (current version)._ - -- [create AWS access keys](/docs/installation/aws-keys.md) - -- setup those keys by running: - - ```bash - serverless config credentials --provider aws --key --secret - ``` - - If you already use the `aws` CLI command, or if you want to use environment variables instead (for example for a shared server like a CI) you can [read the full guide](https://serverless.com/framework/docs/providers/aws/guide/credentials#using-aws-access-keys). - -## Bref - -Install Bref in your project using [Composer](https://getcomposer.org/): - -``` -composer require bref/bref -``` - -> Make sure that the version of Bref that was installed is 2.0 or greater. - -> To run the latest version of Bref you must have PHP 8.0 or greater! If you are using PHP 7.4 or less, an outdated version of Bref will be installed instead. - -The `bref` command line tool can now be used by running `vendor/bin/bref` in your project. - -## What's next? - -Read the [first steps](/docs/first-steps.md) guide to create and deploy your first serverless application using Bref. diff --git a/docs/installation/aws-keys.md b/docs/installation/aws-keys.md deleted file mode 100644 index 597c4833f..000000000 --- a/docs/installation/aws-keys.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -title: Creating AWS access keys -introduction: How to create AWS access keys for Bref. ---- - -To create AWS access key for Bref and the `serverless` CLI tool follow these steps: - -1. In the AWS dashboard, go into IAM and create a new user: [**click here** for a direct link](https://us-east-1.console.aws.amazon.com/iamv2/home#/users/create). - -1. Set a user name (for example "bref-cli") and move to the next screen. - - ![](aws-keys-step-1.png) - -1. Click **Attach policies directly**, search for **AdministratorAccess** and select it. - - ![](aws-keys-step-2.png) - - > **Warning**: the "AdministratorAccess" policy grants full access to your AWS account. This is simpler when starting with AWS and Bref. However, it is recommended to restrict permissions further eventually. - > - > The Serverless documentation has an example of a configuration with stricter permissions: [read this article to create a more secure policy](https://serverless.com/framework/docs/providers/aws/guide/credentials/#creating-aws-access-keys). - -1. Finish creating the user. - -1. Once your user is created, select it and go to **Security credentials**. - - ![](aws-keys-step-3.png) - -1. Scroll down to **Access Keys** and click on **Create access key**. - - ![](aws-keys-step-4.png) - -1. Then select **Command Line Interface**. - - ![](aws-keys-step-5.png) - -1. Add a description to your access keys and click on **Create access key**. - - ![](aws-keys-step-6.png) - -[< Back to the installation guide](/docs/installation.md) diff --git a/docs/laravel/_meta.json b/docs/laravel/_meta.json new file mode 100644 index 000000000..3b790a914 --- /dev/null +++ b/docs/laravel/_meta.json @@ -0,0 +1,7 @@ +{ + "getting-started": "Getting started", + "file-storage": "", + "queues": "Laravel Queues", + "octane": "Laravel Octane", + "passport": "Laravel Passport" +} \ No newline at end of file diff --git a/docs/laravel/caching.mdx b/docs/laravel/caching.mdx new file mode 100644 index 000000000..245a387c6 --- /dev/null +++ b/docs/laravel/caching.mdx @@ -0,0 +1,30 @@ +# Caching + +By default, the Bref bridge will move Laravel's storage and cache directories to `/tmp`. This is because all the filesystem except `/tmp` is read-only. + +However, the `/tmp` directory isn't shared across Lambda instances. If you Lambda function scales up or is redeployed, the cache will be empty in new instances. + +If you want the cache to be shared across all Lambda instances, for example if your application caches a lot of data or if you use it for locking mechanisms (like API rate limiting), you can instead use Redis or DynamoDB. + +DynamoDB is the easiest to set up and is "pay per use". Redis is a bit more complex as it requires a VPC and managing instances, but offers slightly faster response times. + +## DynamoDB Cache + +To use DynamoDB as a cache store, set the following lines in `config/cache.php`: + +```php filename="config/cache.php" {8-11} + 'dynamodb' => [ + 'driver' => 'dynamodb', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), + 'endpoint' => env('DYNAMODB_ENDPOINT'), + 'attributes' => [ + 'key' => 'id', + 'expiration' => 'ttl', + ] + ], +``` + +Then follow [this section of the documentation](/docs/environment/storage.md#deploying-dynamodb-tables) to deploy your DynamoDB table using the Serverless Framework. diff --git a/docs/laravel/file-storage.mdx b/docs/laravel/file-storage.mdx new file mode 100644 index 000000000..d76792e3d --- /dev/null +++ b/docs/laravel/file-storage.mdx @@ -0,0 +1,110 @@ +import { Callout } from 'nextra/components'; + +# File storage + +Laravel has a [filesystem abstraction](https://laravel.com/docs/filesystem) that lets us easily change where files are stored. + +When running on Lambda, you will need to use the **`s3` adapter** to store files on AWS S3. + +To do this, set `FILESYSTEM_DISK: s3` either in `serverless.yml` or your production `.env` file. We can also create an S3 bucket via `serverless.yml` directly: + +```yaml filename="serverless.yml" +# ... +provider: + # ... + environment: + # environment variable for Laravel + FILESYSTEM_DISK: s3 + AWS_BUCKET: !Ref Storage + iam: + role: + statements: + # Allow Lambda to read and write files in the S3 buckets + - Effect: Allow + Action: s3:* + Resource: + - !Sub '${Storage.Arn}' # the storage bucket + - !Sub '${Storage.Arn}/*' # and everything inside + +resources: + Resources: + # Create our S3 storage bucket using CloudFormation + Storage: + Type: AWS::S3::Bucket +``` + +That's it! The AWS credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN) are set automatically in AWS Lambda, you don't have to define them. + +## Public files + +Laravel has a [special disk called `public`](https://laravel.com/docs/filesystem#the-public-disk): this disk stores files that we want to make public, like uploaded photos, generated PDF files, etc. + +Again, those files cannot be stored on Lambda, i.e. they cannot be stored in the default `storage/app/public` directory. You need to store those files on S3. + + + Do not run `php artisan storage:link` in AWS Lambda: it is now useless, and it will fail because the filesystem is read-only in Lambda. + + +To store public files on S3, you could replace the disk in the code: + +```diff +- Storage::disk('public')->put('avatars/1', $fileContents); ++ Storage::disk('s3')->put('avatars/1', $fileContents); +``` + +but doing this will not let your application work locally. A better solution, but more complex, involves making the `public` disk configurable. Let's change the following lines in `config/filesystems.php`: + +```php filename="config/filesystems.php" {7,18,35-43} + /* + |-------------------------------------------------------------------------- + | Default Public Filesystem Disk + |-------------------------------------------------------------------------- + */ + + 'public' => env('FILESYSTEM_DISK', 'public_local'), + + ... + + 'disks' => [ + + 'local' => [ + 'driver' => 'local', + 'root' => storage_path('app'), + ], + + 'public_local' => [ // Rename `public` to `public_local` + 'driver' => 'local', + 'root' => storage_path('app/public'), + 'url' => env('APP_URL').'/storage', + 'visibility' => 'public', + ], + + 's3' => [ + 'driver' => 's3', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'token' => env('AWS_SESSION_TOKEN'), + 'region' => env('AWS_DEFAULT_REGION'), + 'bucket' => env('AWS_BUCKET'), + 'url' => env('AWS_URL'), + ], + + 's3_public' => [ + 'driver' => 's3', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'token' => env('AWS_SESSION_TOKEN'), + 'region' => env('AWS_DEFAULT_REGION'), + 'bucket' => env('AWS_PUBLIC_BUCKET'), + 'url' => env('AWS_URL'), + ], + + ], +``` + +You can now configure the `public` disk to use S3 by changing `serverless.yml` or your production `.env`: + +```bash filename=".env" +FILESYSTEM_DISK=s3 +FILESYSTEM_DISK_PUBLIC=s3 +``` diff --git a/docs/laravel/getting-started.mdx b/docs/laravel/getting-started.mdx new file mode 100644 index 000000000..2a491a1d6 --- /dev/null +++ b/docs/laravel/getting-started.mdx @@ -0,0 +1,97 @@ +import { Callout } from 'nextra/components'; +import { NextSeo } from 'next-seo'; + + + +# Serverless Laravel - Getting started + +This guide helps you run Laravel applications on AWS Lambda using Bref. These instructions are kept up to date to target the latest Laravel version. + + + A demo application is available on GitHub at [github.com/brefphp/examples](https://github.com/brefphp/examples). + + +## Setup + +First, **follow the [Setup guide](../setup.mdx)** to create an AWS account and install the necessary tools. + +Next, in an existing Laravel project, install Bref and the [Laravel-Bref package](https://github.com/brefphp/laravel-bridge). + +```bash +composer require bref/bref bref/laravel-bridge --update-with-dependencies +``` + +Then let's create a [`serverless.yml` configuration file](../environment/serverless-yml.mdx): + +```bash +php artisan vendor:publish --tag=serverless-config +``` + +### How it works + +By default, the Laravel-Bref package will automatically configure Laravel to work on AWS Lambda. + +If you are curious, the package will automatically: + +- enable the `stderr` log driver, to send logs to CloudWatch ([read more about logs](../environment/logs.md)) +- enable the [`cookie` session driver](https://laravel.com/docs/session#configuration) (if you prefer, you can configure sessions to be stored in database, DynamoDB or Redis) +- move the storage directory to `/tmp` (because the default storage directory is read-only on Lambda) +- adjust a few more settings ([have a look at the `BrefServiceProvider` for details](https://github.com/brefphp/laravel-bridge/blob/master/src/BrefServiceProvider.php)) + +## Deployment + +We do not want to deploy "dev" caches that were generated on our machine (because paths will be different on AWS Lambda). Let's clear them before deploying: + +```bash +php artisan config:clear +``` + +When running in AWS Lambda, the Laravel application will automatically cache its configuration when booting. You don't need to run `php artisan config:cache` before deploying. + +Let's deploy now: + +```bash +serverless deploy +``` + +When finished, the `deploy` command will show the URL of the application. + +### Deploying for production + +At the moment, we deployed our local codebase to Lambda. When deploying for production, we probably don't want to deploy: + +- development dependencies, +- our local `.env` file, +- or any other dev artifact. + +Follow [the deployment guide](/docs/deploy.md#deploying-for-production) for more details. + +## Troubleshooting + +In case your application is showing a blank page after being deployed, [have a look at the logs](../environment/logs.md). + +## Website assets + +Have a look at the [Website guide](../use-cases/websites.mdx) to learn how to deploy a website with assets. + +## Laravel Artisan + +As you may have noticed, we define a function named "artisan" in `serverless.yml`. That function is using the [Console runtime](../runtimes/console.mdx), which lets us run Laravel Artisan on AWS Lambda. + +For example, to execute an `artisan` command on Lambda, run the command below: + +```bash +serverless bref:cli --args="" +``` + +For example: + +```bash +serverless bref:cli --args="route:list" +``` + +For more details follow [the "Console" guide](../runtimes/console.mdx). + +## Inertia + +Laravel with Inertia runs without issue, like any other website. Follow the [Websites guide](../use-cases/websites.mdx) to learn how to deploy a Laravel with assets with Bref. diff --git a/docs/laravel/maintenance-mode.mdx b/docs/laravel/maintenance-mode.mdx new file mode 100644 index 000000000..d4bc50cee --- /dev/null +++ b/docs/laravel/maintenance-mode.mdx @@ -0,0 +1,23 @@ +# Maintenance mode + +Similar to the `php artisan down` command, you may put your app into maintenance mode. All that's required is setting the `MAINTENANCE_MODE` environment variable: + +```yml filename="serverless.yml" +provider: + environment: + MAINTENANCE_MODE: ${param:maintenance, null} +``` + +You can then deploy: + +```bash +# Full deployment (goes through CloudFormation): +serverless deploy --param="maintenance=1" + +# Or quick update of the functions config only: +serverless deploy function --function=web --update-config --param="maintenance=1" +serverless deploy function --function=artisan --update-config --param="maintenance=1" +serverless deploy function --function= --update-config --param="maintenance=1" +``` + +To take your app out of maintenance mode, redeploy without the `--param="maintenance=1"` option. diff --git a/docs/laravel/octane.mdx b/docs/laravel/octane.mdx new file mode 100644 index 000000000..60517beb0 --- /dev/null +++ b/docs/laravel/octane.mdx @@ -0,0 +1,45 @@ +import { NextSeo } from 'next-seo'; + + + +# Laravel Octane + +To run the HTTP application with [Laravel Octane](https://laravel.com/docs/10.x/octane) instead of PHP-FPM, change the following options in the `web` function: + +```yml +functions: + web: + handler: Bref\LaravelBridge\Http\OctaneHandler + runtime: php-81 + environment: + BREF_LOOP_MAX: 250 + # ... +``` + +Keep the following details in mind: + +- Laravel Octane does not need Swoole or RoadRunner on AWS Lambda, so it is not possible to use Swoole-specific features. +- Octane keeps Laravel booted in a long-running process, [beware of memory leaks](https://laravel.com/docs/10.x/octane#managing-memory-leaks). +- The process is kept alive between requests, but you still don't pay for time between requests. The execution model and cost model of AWS Lambda does not change (Lambda is frozen between requests). +- `BREF_LOOP_MAX` specifies the number of HTTP requests handled before the PHP process is restarted (and the memory is cleared). + +## Persistent database connections + +You can keep database connections persistent across requests to make your application even faster. To do so, set the `OCTANE_PERSIST_DATABASE_SESSIONS` environment variable: + +```yml +functions: + web: + handler: Bref\LaravelBridge\Http\OctaneHandler + runtime: php-81 + environment: + BREF_LOOP_MAX: 250 + OCTANE_PERSIST_DATABASE_SESSIONS: 1 + # ... +``` + +Note that if you are using PostgreSQL (9.6 or newer), you need to set [`idle_in_transaction_session_timeout`](https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-IDLE-IN-TRANSACTION-SESSION-TIMEOUT) either in your RDS database's parameter group, or on a specific database itself. + +```sql +ALTER DATABASE SET idle_in_transaction_session_timeout = '10000' -- 10 seconds in ms +``` diff --git a/docs/laravel/passport.mdx b/docs/laravel/passport.mdx new file mode 100644 index 000000000..142502b81 --- /dev/null +++ b/docs/laravel/passport.mdx @@ -0,0 +1,56 @@ +import { Steps } from 'nextra/components'; +import { NextSeo } from 'next-seo'; + + + +# Laravel Passport + +Laravel Passport has a `passport:install` command. However, this command cannot be run in Lambda because it needs to write files to the `storage/` directory. + +Instead, here is what you need to do: + + + + ### Generate keys locally + + Run the following command on your machine to generate key files: + + ```bash + php artisan passport:keys + ``` + + This will generate the `storage/oauth-private.key` and `storage/oauth-public.key` files, which need to be deployed. + + Depending on how you deploy your application (from your machine, or from CI), you may want to whitelist them in `serverless.yml`: + + ```yml filename="serverless.yml" + package: + patterns: + - ... + # Exclude the 'storage' directory + - '!storage/**' + # Except the public and private keys required by Laravel Passport + - 'storage/oauth-private.key' + - 'storage/oauth-public.key' + ``` + + ### Deploy + + You can now redeploy the application: + + ```yaml + serverless deploy + ``` + + ### Create tokens + + Finally, you can create the tokens (which is the second part of the `passport:install` command): + + ```bash + serverless bref:cli --args="passport:client --personal --name 'Laravel Personal Access Client'" + serverless bref:cli --args="passport:client --password --name 'Laravel Personal Access Client'" + ``` + + + +All these steps were replacements of running the `passport:install` command [from the Passport documentation](https://laravel.com/docs/passport#installation). diff --git a/docs/laravel/queues.mdx b/docs/laravel/queues.mdx new file mode 100644 index 000000000..098882689 --- /dev/null +++ b/docs/laravel/queues.mdx @@ -0,0 +1,69 @@ +import { Callout } from 'nextra/components'; +import { NextSeo } from 'next-seo'; + + + +# Laravel Queues + + + A demo application is available on GitHub at [github.com/brefphp/examples](https://github.com/brefphp/examples). + + +To run Laravel Queues on AWS Lambda using [Amazon SQS](https://aws.amazon.com/sqs/), we don't want to run the `php artisan queue:work` command. Instead, we create a Lambda function that is invoked immediately when there are new jobs to process. + +To create the SQS queue (and the permissions for the Lambda functions to read/write to it), we can either do that manually, or use `serverless.yml`. + +To make things simpler, we will use the [Serverless Lift](https://github.com/getlift/lift) plugin to create and configure the SQS queue. + +First install the Lift plugin: + +```bash +serverless plugin install -n serverless-lift +``` + +Then use [the Queue construct](https://github.com/getlift/lift/blob/master/docs/queue.md) in `serverless.yml`: + +```yml filename="serverless.yml" +provider: + # ... + environment: + # ... + QUEUE_CONNECTION: sqs + SQS_QUEUE: ${construct:jobs.queueUrl} + +functions: + # ... + +constructs: + jobs: + type: queue + worker: + handler: Bref\LaravelBridge\Queue\QueueHandler + runtime: php-81 + timeout: 60 # seconds +``` + +We define Laravel environment variables in `provider.environment` (this could also be done in the deployed `.env` file): + +- `QUEUE_CONNECTION: sqs` enables the SQS queue connection +- `SQS_QUEUE: ${construct:jobs.queueUrl}` passes the URL of the created SQS queue + +If you want to create the SQS queue manually, you will need to set these variables. AWS credentials (`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`) are automatically set up with the appropriate permissions for Laravel to use the SQS queue. + +That's it! Anytime a job is pushed to Laravel Queues, it will be sent to SQS, and SQS will invoke our "worker" function so that it is processed. + + + In the example above, we set the full SQS queue URL in the `SQS_QUEUE` variable. + + If you only set the queue name (which is also valid), you need to set the `SQS_PREFIX` environment variable too. For example: `SQS_PREFIX: "https://sqs.${aws:region}.amazonaws.com/${aws:accountId}"`. + + +## How it works + +When integrated with AWS Lambda, SQS has a built-in retry mechanism and storage for failed messages. These features work slightly differently than Laravel Queues. The "Bref for Laravel" integration does **not** use these SQS features. + +Instead, "Bref for Laravel" makes all the feature of Laravel Queues work out of the box, just like on any server. Read more in [the Laravel Queues documentation](https://laravel.com/docs/latest/queues). + + + The "Bref-Laravel bridge" v1 used to do the opposite. We changed that behavior in Bref v2 in order to make the experience smoother for Laravel users. + diff --git a/docs/web-apps/local-development.md b/docs/local-development.mdx similarity index 54% rename from docs/web-apps/local-development.md rename to docs/local-development.mdx index 4bbdace59..ceacbf6a0 100644 --- a/docs/web-apps/local-development.md +++ b/docs/local-development.mdx @@ -1,37 +1,43 @@ ---- -title: Local development for web apps -current_menu: web-local-development -previous: - link: /docs/web-apps/cron.html - title: Cron commands -next: - link: /docs/web-apps/docker.html - title: Docker ---- +import { Tabs, Tab, Callout } from 'nextra/components'; -It is possible to run **web applications** locally. +# Local development -> To run event-driven **PHP functions** locally, see [Local development for PHP Functions](/docs/function/local-development.md) instead. +It is possible to run **web applications** (running with the FPM runtime) locally. + + + To run **event-driven functions** locally, read [Local development for event-driven functions](./local-development/event-driven-functions.mdx) instead. + ## The simple way -To keep things simple, you can run your PHP application like you did without Bref. For example with your favorite framework: +To keep things simple, you can run your applications locally like you did without Bref. -- Laravel via `php artisan serve` or [Homestead](https://laravel.com/docs/homestead) or [Laravel Valet](https://laravel.com/docs/valet) -- Symfony via `symfony server:start` ([documentation](https://symfony.com/doc/current/setup/symfony_server.html)) + + + With **Laravel**, run HTTP applications locally using `php artisan serve`, [Laravel Valet](https://laravel.com/docs/valet), or [Laravel Sail](https://laravel.com/docs/10.x/sail). -If you are not using any framework, you can use PHP's built-in server: + You can test CLI commands locally by running them in your terminal using `php artisan my-command`. + + + With **Symfony**, run HTTP applications locally using `symfony server:start` ([documentation](https://symfony.com/doc/current/setup/symfony_server.html)). -```bash -php -S localhost:8000 -# The application is now available at http://localhost:8000/ -``` + You can test CLI commands locally by running them in your terminal using `bin/console my-command`. + + + Run your HTTP applications locally via your preferred method to run PHP: Apache, WAMP, or even the built-in PHP server: + + ```bash + php -S localhost:8000 + # The application is now available at http://localhost:8000/ + ``` + + ## Docker -In order to run the application locally in an environment closer to production, you can use the [Bref Docker images](https://hub.docker.com/u/bref). For example, create the following `docker-compose.yml`: +In order to run the application locally in an environment closer to production, you can run your application using the [Bref Docker images](https://hub.docker.com/u/bref). For example, create the following `docker-compose.yml`: -```yaml +```yml filename="docker-compose.yml" version: "3.5" services: @@ -42,9 +48,17 @@ services: - .:/var/task environment: HANDLER: public/index.php + # Assets will be served from this directory + DOCUMENT_ROOT: public ``` -After running `docker-compose up`, the application will be available at [http://localhost:8000/](http://localhost:8000/). +You can then run: + +```bash +docker-compose up +``` + +The application will be available at [http://localhost:8000/](http://localhost:8000/). The `HANDLER` environment variable lets you define which PHP file will be handling all HTTP requests. This should be the same handler that you have defined in `serverless.yml` for your HTTP function. @@ -56,15 +70,15 @@ The code will be mounted in `/var/task`, just like in Lambda. But in Lambda, `/v When developing locally, it is common to regenerate cache files on the fly (for example Symfony or Laravel cache). You have 2 options: -- mount the whole codebase as writable: +- either mount the whole codebase as writable (per the example above): - ```yaml + ```yaml filename="docker-compose.yml" volumes: - .:/var/task ``` -- mount a specific cache directory as writable (better): +- or mount a specific cache directory as writable (better): - ```yaml + ```yaml filename="docker-compose.yml" {3} volumes: - .:/var/task:ro - ./storage:/var/task/storage @@ -74,23 +88,33 @@ When developing locally, it is common to regenerate cache files on the fly (for If you want to serve assets locally, you can define a `DOCUMENT_ROOT` environment variable: -```yaml -version: "3.5" - +```yaml {6,7} filename="docker-compose.yml" services: app: - image: bref/php-81-fpm-dev:2 - ports: [ '8000:8000' ] - volumes: - - .:/var/task + # ... environment: HANDLER: public/index.php + # Assets will be served from this directory DOCUMENT_ROOT: public ``` In the example above, a `public/assets/style.css` file will be accessible at `http://localhost:8000/assets/style.css`. -> Be aware that serving assets in production will not work like this out of the box. You will need [to use a S3 bucket](/docs/runtimes/http.md#assets). + + Be aware that serving assets in production will not work like this out of the box. You will need [to use an S3 bucket](./use-cases/websites.mdx). + + +### Console commands + +You can run console commands in Docker via: + +```bash +# Laravel (artisan) +docker-compose run app php artisan ... + +# Symfony (bin/console) +docker-compose run app php bin/console ... +``` ### Xdebug @@ -98,7 +122,7 @@ The development container (`bref/php--fpm-dev`) comes with Xdebug pre-i To enable it, create a `php/conf.dev.d/php.ini` file in your project containing: -```ini +```ini filename="php/conf.dev.d/php.ini" zend_extension=xdebug.so ``` @@ -110,7 +134,7 @@ Docker for Mac uses a virtual machine for running docker. That means you need to Edit the `php/conf.dev.d/php.ini` file: -```ini +```ini filename="php/conf.dev.d/php.ini" {3-6} zend_extension=xdebug.so [xdebug] @@ -123,7 +147,7 @@ xdebug.remote_host = 'host.docker.internal' The development container (`bref/php--fpm-dev`) comes with the [blackfire](https://www.blackfire.io/) extension. When using docker compose, you can add the following service for the blackfire agent: -```yaml +```yml filename="docker-compose.yml" services: blackfire: image: blackfire/blackfire @@ -134,40 +158,9 @@ services: In order to enable the probe you can create a folder `php/conf.dev.d` in your project and include an ini file enabling blackfire: -```ini +```ini filename="php/conf.dev.d/php.ini" extension=blackfire blackfire.agent_socket=tcp://blackfire:8707 ``` For more details about using blackfire in a docker environment see the [blackfire docs](https://blackfire.io/docs/integrations/docker) - -## Console applications - -Console applications can be tested just like before: by running the command in your terminal. - -For example with Symfony you can run `bin/console ` , or with Laravel run `php artisan `. - -If you want to run your console in an environment close to production, you can use the Bref Docker dev images documented above. For example, if you have a `docker-compose.yml` file like this: - -```yaml -version: "3.5" - -services: - app: - image: bref/php-81-fpm-dev:2 - ports: [ '8000:8000' ] - volumes: - - .:/var/task - environment: - HANDLER: public/index.php -``` - -Then CLI commands can be run in Docker via: - -```bash -# Symfony (bin/console) -docker-compose run app php bin/console - -# Laravel (artisan) -docker-compose run app php artisan -``` diff --git a/docs/local-development/_meta.json b/docs/local-development/_meta.json new file mode 100644 index 000000000..209b7d435 --- /dev/null +++ b/docs/local-development/_meta.json @@ -0,0 +1,3 @@ +{ + "event-driven-functions": "Event-driven functions" +} \ No newline at end of file diff --git a/docs/function/local-development.md b/docs/local-development/event-driven-functions.mdx similarity index 59% rename from docs/function/local-development.md rename to docs/local-development/event-driven-functions.mdx index 6fe40633e..d7464c3ef 100644 --- a/docs/function/local-development.md +++ b/docs/local-development/event-driven-functions.mdx @@ -1,35 +1,30 @@ ---- -title: Local development for functions -current_menu: function-local-development -previous: - link: /docs/function/handlers.html - title: Typed handlers -next: - link: /docs/function/cron.html - title: Cron functions ---- - -It is possible to run **PHP functions** locally. - -> **Warning:** -> To run **web apps** locally, see [Local development for HTTP applications](/docs/web-apps/local-development.md) instead. +import { Callout } from 'nextra/components'; + +# Local development for functions + +It is possible to run **event-driven functions** locally. + + + To run **HTTP applications** (like Laravel or Symfony) locally, read [Local development for HTTP applications](../local-development.mdx) instead. + ## With Serverless Framework The `serverless bref:local` command invokes your [PHP functions](/docs/runtimes/function.md) locally, using PHP installed on your machine. You can provide an event if your function expects one. -> **Note:** -> The `serverless bref:local` command is a simpler alternative to the native `serverless invoke local` command, which tries to run PHP using Docker with very little success. Use `bref:local` instead of `invoke local`. + + The `serverless bref:local` command is a simpler alternative to the native `serverless invoke local` command, which tries to run PHP using Docker with very little success. Use `serverless bref:local` instead of `serverless invoke local`. + For example, given this function: -```php +```php filename="my-function.php" return function (array $event) { return 'Hello ' . ($event['name'] ?? 'world'); }; ``` -```yaml +```yml filename="serverless.yml" # ... functions: @@ -53,18 +48,14 @@ $ serverless bref:local -f hello --path=event.json Hello Jane ``` -> **Note:** On Windows PowerShell, you must escape the "double quote" char if you write JSON directly in the CLI. Example: -> ```bash -> $ serverless bref:local -f hello --data '{\"name\": \"Bill\"}' -> ``` - -The `serverless bref:local` command runs using the local PHP installation. If you prefer to use Docker, check out the "Without Serverless Framework" section below. - -## API Gateway local development - -If you build HTTP applications with [API Gateway HTTP events](handlers.md#api-gateway-http-events), `serverless bref:local` is a bit unpractical because you need to manually craft HTTP events in JSON. + + On Windows PowerShell, you must escape the "double quote" char if you write JSON directly in the CLI. Example: + ```bash + $ serverless bref:local -f hello --data '{\"name\": \"Bill\"}' + ``` + -Instead, you can use the [`bref/dev-server`](https://github.com/brefphp/dev-server) package to emulate API Gateway locally. +The `serverless bref:local` command runs using the local PHP installation. If you prefer to use **Docker**, check out the "Without Serverless Framework" section below. ## Without Serverless Framework @@ -82,7 +73,7 @@ $ vendor/bin/bref-local my-function.php '{"name": "Jane"}' Hello Jane # With a path to a file containing a JSON event. -$ cat event.json +$cat event.json { "name": "Alex" } @@ -90,6 +81,8 @@ $ vendor/bin/bref-local --path event.json my-function.php Hello Alex ``` +## With Docker + If you want to run your function in Docker: ```bash @@ -99,9 +92,9 @@ $ docker run --rm -it --entrypoint= -v $(PWD):/var/task:ro bref/php-81:2 vendor/ $ docker run --rm -it -v $(PWD):/var/task:ro bref/php-81-fpm-dev:2 vendor/bin/bref-local my-function.php ``` -You can also use Docker Compose: +You can also use Docker Compose, like described in [Local development for HTTP applications](../local-development.mdx): -```yaml +```yml filename="docker-compose.yml" version: "3.5" services: app: @@ -115,3 +108,9 @@ Then run functions: ```bash $ docker-compose run app vendor/bin/bref-local my-function.php ``` + +## API Gateway local development + +If you build HTTP applications with [API Gateway HTTP events](../function/handlers.md#api-gateway-http-events), `serverless bref:local` is a bit unpractical because you need to manually craft HTTP events in JSON. + +Instead, you can use the [`bref/dev-server`](https://github.com/brefphp/dev-server) package to emulate API Gateway locally. diff --git a/docs/monitoring.md b/docs/monitoring.md index 0bdfa8898..e3d928e2d 100644 --- a/docs/monitoring.md +++ b/docs/monitoring.md @@ -1,7 +1,4 @@ ---- -title: Monitoring -current_menu: monitoring ---- +# Monitoring By default, AWS Lambda publishes all logs and general metrics (HTTP response time, code execution duration, etc.) to AWS CloudWatch. diff --git a/docs/newsletters.md b/docs/newsletters.md deleted file mode 100644 index f70c75ee7..000000000 --- a/docs/newsletters.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: Newsletters -current_menu: newsletters -introduction: A collection of newsletters related to serverless and PHP. ---- - -This page collects newsletters related to serverless and PHP. Those are not necessarily related to Bref. - -You can subscribe to these newsletters to keep up to date with what's happening in the serverless world. - -- [Serverless PHP](https://serverless-php.news/) - - A monthly newsletter about serverless news related to PHP. -- [Off by None](https://www.jeremydaly.com/newsletter/) - - A weekly newsletter about serverless news in general. diff --git a/docs/runtimes.mdx b/docs/runtimes.mdx new file mode 100644 index 000000000..48255fe97 --- /dev/null +++ b/docs/runtimes.mdx @@ -0,0 +1,192 @@ +import { Callout } from 'nextra/components'; +import { NextSeo } from 'next-seo'; + + + +# PHP runtimes for AWS Lambda + +There is no native support for PHP on AWS Lambda. Instead, we can use third-party runtimes via [AWS Lambda *custom runtimes*](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html). + +**Bref provides open-source runtimes to run PHP on Lambda**. These PHP runtimes are distributed as AWS Lambda layers and Docker images. + +## Bref runtimes + +Bref provides 3 PHP runtimes: + +- The "FPM" runtime, to run **web applications**. +- The "function" runtime, to run **event-driven functions**. +- The "console" runtime, to run **CLI commands**. + +These runtimes are used by the Laravel & Symfony framework integrations to run web applications, console/artisan commands, queues, and more. + +The runtimes are available as AWS Lambda layers that you can use (explained below). They are also published as Docker images so that you can run your applications locally (more on that later). + +### PHP-FPM runtime for web apps + +Name: `php-83-fpm`, `php-82-fpm`, `php-81-fpm`, and `php-80-fpm`. + +This runtime uses PHP-FPM to run **web applications** on AWS Lambda, like on a traditional server. + +It's **the easiest to start with**: it works like traditional PHP hosting and is compatible with Symfony, Laravel, and other frameworks. + +[Learn more about the PHP-FPM runtime](./runtimes/fpm-runtime.mdx). + +### Event-driven functions + +Name: `php-83`, `php-82`, `php-81`, and `php-80`. + +AWS Lambda was initially created to run _functions_ (yes, functions of code) in the cloud. + +The Bref "function" runtime lets you create Lambda functions in PHP like with any other language. + +This runtime works great to create **event-driven micro-services**. + + + If you are getting started, we highly recommend using the FPM runtime instead. It's "PHP as usual" (like on any server), with all the benefits of serverless (simplicity, scaling, etc.). + + +[Learn more about the Function runtime](./runtimes/function.mdx). + +### Console + +Name: `php-83-console`, `php-82-console`, `php-81-console`, and `php-80-console`. + +This runtime lets you run CLI console commands on Lambda. + +For example, we can run the [Symfony Console](https://symfony.com/doc/master/components/console.html) or [Laravel Artisan](https://laravel.com/docs/artisan). + +[Learn more about the Console runtime](./runtimes/console.mdx). + +## Usage + +To use a runtime, set it on each function in `serverless.yml`: + +```yaml +service: app +provider: + name: aws +plugins: + - ./vendor/bref/bref +functions: + hello: + # ... + runtime: php-81 + # or: + runtime: php-81-fpm + # or: + runtime: php-81-console +``` + +Bref currently provides runtimes for PHP 8.0, 8.1, 8.2 and 8.3: + +- `php-83` +- `php-82` +- `php-81` +- `php-80` +- `php-83-fpm` +- `php-82-fpm` +- `php-81-fpm` +- `php-80-fpm` +- `php-83-console` +- `php-82-console` +- `php-81-console` +- `php-80-console` + + + `php-80` means PHP 8.0.\*. It is not possible to require a specific "patch" version. The latest Bref versions always aim to support the latest PHP versions, so upgrade via Composer frequently to keep PHP up to date. + + +### The Bref plugin for serverless.yml + +Make sure to always include the Bref plugin in your `serverless.yml` config: + +```yaml +plugins: + - ./vendor/bref/bref +``` + +This plugin is what makes `runtime: php-81` work (as well as other utilities). It is explained in more details in the section below. + +### ARM runtimes + +It is possible to run AWS Lambda functions on [ARM-based AWS Graviton processors](https://aws.amazon.com/blogs/aws/aws-lambda-functions-powered-by-aws-graviton2-processor-run-your-functions-on-arm-and-get-up-to-34-better-price-performance/). This is usually considered a way to reduce costs and improve performance. + +You can deploy to ARM by using the `arm64` architecture: + +```diff +functions: + api: + handler: public/index.php + runtime: php-81-fpm ++ architecture: arm64 +``` + +The Bref plugin will detect that change and automatically use the Bref ARM Lambda layers. + + + The `bref-extra-extensions` package is not available for ARM processors yet. + + +### AWS Lambda layers + +The `runtime: php-xxx` runtimes we use in `serverless.yml` are not _real_ AWS Lambda runtimes. Indeed, PHP is not supported natively on AWS Lambda. + +What the Bref plugin for `serverless.yml` (the one we include with `./vendor/bref/bref`) does is it automatically turns this: + +```yaml +functions: + hello: + # ... + runtime: php-81 +``` + +into this: + +```yaml +functions: + hello: + # ... + runtime: provided.al2 + layers: + - 'arn:aws:lambda:us-east-1:534081306603:layer:php-81:21' +``` + +☝️ `provided.al2` [is the generic Linux environment for custom runtimes](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html#runtimes-custom-use), and the `layers` config points to Bref's AWS Lambda layers. + +Thanks to the Bref plugin, our `serverless.yml` is simpler. It also automatically adapts to the AWS region in use, and automatically points to the correct layer version. You can learn more about "layers" [in this page](./runtimes/runtimes-details.mdx). + +If you want to reference AWS Lambda layers directly (instead of using the simpler `runtime: php-81` syntax), the Bref plugin also provides simple `serverless.yml` variables. These were the default in Bref v1.x, so you may find this older syntax on tutorials and blog posts: + +```yaml +service: app +provider: + name: aws + runtime: provided.al2 +plugins: + - ./vendor/bref/bref +functions: + hello: + # ... + layers: + - ${bref:layer.php-81} + # or: + - ${bref:layer.php-81-fpm} +``` + +The `${...}` notation is the [syntax to use variables](https://serverless.com/framework/docs/providers/aws/guide/variables/) in `serverless.yml`. The Bref plugin provides the following variables: + +- `${bref:layer.php-83}` +- `${bref:layer.php-82}` +- `${bref:layer.php-81}` +- `${bref:layer.php-80}` +- `${bref:layer.php-83-fpm}` +- `${bref:layer.php-82-fpm}` +- `${bref:layer.php-81-fpm}` +- `${bref:layer.php-80-fpm}` +- `${bref:layer.console}` + +Bref ARM layers are the same as the x86 layers, but with the `arm-` prefix in their name, for example `${bref:layer.arm-php-82}`. The only exception is `${bref:layer.console}` (this is the same layer for both x86 and ARM). + + + To be clear, it is easier and recommended to use the `runtime: php-xxx` option instead of setting `layers` directly. + diff --git a/docs/runtimes/README.md b/docs/runtimes/README.md deleted file mode 100644 index e6a0c3a6d..000000000 --- a/docs/runtimes/README.md +++ /dev/null @@ -1,294 +0,0 @@ ---- -title: PHP runtimes for AWS Lambda -current_menu: runtimes-introduction -introduction: Bref provides runtimes to bring support for PHP on AWS Lambda. -previous: - link: /docs/first-steps.html - title: First steps -next: - link: /docs/runtimes/http.html - title: Web apps on AWS Lambda ---- - -There is no built-in support for PHP on AWS Lambda. Instead, we can use 3rd party runtimes via [AWS Lambda *layers*](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html). - -**Bref provides open-source runtimes to run PHP on Lambda** (distributed as AWS Lambda layers). - -## Bref runtimes - -Bref provides 2 main runtimes: - -- The "FPM" runtime, to create **web applications**. -- The "function" runtime, to create **event-driven functions**. - -You can see in the documentation menu how these two runtimes are used for two different kinds of applications. - -These runtimes are available as AWS Lambda layers that you can use (explained below). They are also published as Docker images so that you can run your applications locally (more on that later). - -### Web apps - -Name: `php-83-fpm`, `php-82-fpm`, `php-81-fpm`, and `php-80-fpm`. - -This runtime uses PHP-FPM to run **web applications** on AWS Lambda. - -It's **the easiest to start with**: it works like traditional PHP hosting and is compatible with Symfony and Laravel. - -[Get started with the FPM runtime in "Bref for web apps"](/docs/runtimes/http.md). - -### Event-driven functions - -Name: `php-83`, `php-82`, `php-81`, and `php-80`. - -AWS Lambda was initially created to run _functions_ (yes, functions of code) in the cloud. - -The Bref "function" runtime lets you create Lambda functions in PHP like with any other language. - -This runtime works great to create **event-driven micro-services**. - -_Note: if you are getting started, we highly recommend using the FPM runtime instead. It's "PHP as usual" (like on any server), with all the benefits of serverless (simplicity, scaling, etc.)._ - -[Get started with the Function runtime in "Bref for event-driven functions"](/docs/runtimes/function.md). - -### Console - -Name: `php-83-console`, `php-82-console`, `php-81-console`, and `php-80-console`. - -This runtime lets you run CLI console commands on Lambda. - -For example, we can run the [Symfony Console](https://symfony.com/doc/master/components/console.html) or [Laravel Artisan](https://laravel.com/docs/artisan). - -[Read more about the `console` runtime here](/docs/runtimes/console.md). - -## Usage - -To use a runtime, set it on each function in `serverless.yml`: - -```yaml -service: app -provider: - name: aws -plugins: - - ./vendor/bref/bref -functions: - hello: - # ... - runtime: php-81 - # or: - runtime: php-81-fpm - # or: - runtime: php-81-console -``` - -Bref currently provides runtimes for PHP 8.0, 8.1, 8.2 and 8.3: - -- `php-83` -- `php-82` -- `php-81` -- `php-80` -- `php-83-fpm` -- `php-82-fpm` -- `php-81-fpm` -- `php-80-fpm` -- `php-83-console` -- `php-82-console` -- `php-81-console` -- `php-80-console` - -> `php-80` means PHP 8.0.\*. It is not possible to require a specific "patch" version. The latest Bref versions always aim to support the latest PHP versions, so upgrade frequently to keep PHP up to date. - -### ARM runtimes - -It is possible to run AWS Lambda functions on [ARM-based AWS Graviton processors](https://aws.amazon.com/blogs/aws/aws-lambda-functions-powered-by-aws-graviton2-processor-run-your-functions-on-arm-and-get-up-to-34-better-price-performance/). This is usually considered a way to reduce costs and improve performance. - -You can deploy to ARM by using the `arm64` architecture: - -```diff -functions: - api: - handler: public/index.php - runtime: php-81-fpm -+ architecture: arm64 -``` - -The Bref plugin will detect that change and automatically use the Bref ARM Lambda layers. - -### The Bref plugin for serverless.yml - -Make sure to always include the Bref plugin in your `serverless.yml` config: - -```yaml -plugins: - - ./vendor/bref/bref -``` - -This plugin is what makes `runtime: php-81` work (as well as other utilities). It is explained in more details in the section below. - -### AWS Lambda layers - -The `runtime: php-xxx` runtimes we use in `serverless.yml` are not _real_ AWS Lambda runtimes. Indeed, PHP is not supported natively on AWS Lambda. - -What the Bref plugin for `serverless.yml` (the one we include with `./vendor/bref/bref`) does is it automatically turns this: - -```yaml -functions: - hello: - # ... - runtime: php-81 -``` - -into this: - -```yaml -functions: - hello: - # ... - runtime: provided.al2 - layers: - - 'arn:aws:lambda:us-east-1:534081306603:layer:php-81:21' -``` - -☝️ `provided.al2` [is the generic Linux environment for custom runtimes](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html#runtimes-custom-use), and the `layers` config points to Bref's AWS Lambda layers. - -Thanks to the Bref plugin, our `serverless.yml` is simpler. It also automatically adapts to the AWS region in use, and automatically points to the correct layer version. You will learn more about "layers" below in this page. - -If you want to reference AWS Lambda layers directly (instead of using the simpler `runtime: php-81` syntax), the Bref plugin also provides simple `serverless.yml` variables. These were the default in Bref v1.x, so you may find this older syntax on tutorials and blog posts: - -```yaml -service: app -provider: - name: aws - runtime: provided.al2 -plugins: - - ./vendor/bref/bref -functions: - hello: - # ... - layers: - - ${bref:layer.php-80} - # or: - - ${bref:layer.php-80-fpm} -``` - -The `${...}` notation is the [syntax to use variables](https://serverless.com/framework/docs/providers/aws/guide/variables/) in `serverless.yml`. The Bref plugin provides the following variables: - -- `${bref:layer.php-83}` -- `${bref:layer.php-82}` -- `${bref:layer.php-81}` -- `${bref:layer.php-80}` -- `${bref:layer.php-83-fpm}` -- `${bref:layer.php-82-fpm}` -- `${bref:layer.php-81-fpm}` -- `${bref:layer.php-80-fpm}` -- `${bref:layer.console}` - -Bref ARM layers are the same as the x86 layers, but with the `arm-` prefix in their name, for example `${bref:layer.arm-php-82}`. The only exception is `${bref:layer.console}` (this is the same layer for both x86 and ARM). - -> **Note**: to be clear, it is easier and recommended to use the `runtime: php-xxx` option instead of setting `layers` directly. - -## Lambda layers in details - -> **Notice**: this section is only useful if you want to learn more. -> -> You can skip it for now if you just want to get started with Bref. -> -> ▶ [**Get started with web apps**](/docs/runtimes/http.md). - -Bref runtimes are distributed as [AWS Lambda layers](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html). While Bref provides a Serverless plugin to simplify how to use them, you can use the layers directly. - -The layer names follow this pattern: - -``` -arn:aws:lambda::534081306603:layer:: - -# For example: -arn:aws:lambda:us-east-1:534081306603:layer:php-80:21 -``` - -You can use layers via their full ARN, for example in `serverless.yml`: - -```yaml -service: app -provider: - name: aws - runtime: provided.al2 -functions: - hello: - ... - layers: - - 'arn:aws:lambda:us-east-1:534081306603:layer:php-80:21' -``` - -Or if you are using [SAM's `template.yaml`](https://aws.amazon.com/serverless/sam/): - -```yaml -AWSTemplateFormatVersion: '2010-09-09' -Transform: AWS::Serverless-2016-10-31 -Resources: - Hello: - Type: AWS::Serverless::Function - Properties: - ... - Runtime: provided.al2 - Layers: - - 'arn:aws:lambda:us-east-1:534081306603:layer:php-80:21' -``` - -Bref layers work with AWS Lambda regardless of the tool you use to deploy your application: Serverless, SAM, CloudFormation, Terraform, AWS CDK, etc. - -> Remember: the layer ARN contains a region. **You need to use the same region as the rest of your application** else Lambda will not find the layer. - -### Layers NPM package - -You can use [the `@bref.sh/layers.js` NPM package](https://github.com/brefphp/layers.js) to get up-to-date layer ARNs in Node applications, for example with the AWS CDK. - -### Layer version (``) - -The latest of runtime versions can be found at [**runtimes.bref.sh**](https://runtimes.bref.sh/). - -Here are the latest versions: - - - -You can also find the appropriate ARN/version for your current Bref version by running: - -```bash -serverless bref:layers -``` - -**Watch out:** if you use the layer ARN directly, you may need to update the ARN (the `` part) when you update Bref. Follow the Bref release notes closely. - -### Bref ping - -Bref layers send a ping to estimate the total number of Lambda invocations powered by Bref. That statistic is useful in two ways: - -- to provide new users an idea on how much Bref is used in production -- to communicate to AWS how much Bref is used and push for better PHP integration with AWS Lambda tooling - -We consider this to be beneficial both to the Bref project (by getting more users and more consideration from AWS) and for Bref users (more users means a larger community, a stronger and more active project, as well as more features from AWS). - -#### What is sent - -The data sent in the ping is completely anonymous. It does not contain any identifiable data about anything (the project, users, etc.). - -**The only data it contains is:** "A Bref invocation happened with the layer XYZ" (where XYZ is the name of the Bref layer, like "function", "fpm" or "console"). - -Anyone can inspect the code and the data sent by checking the [`Bref\Runtime\LambdaRuntime::ping()` function](https://github.com/brefphp/bref/blob/master/src/Runtime/LambdaRuntime.php#L374). - -#### How is it sent - -The data is sent via the [statsd](https://github.com/statsd/statsd) protocol, over [UDP](https://en.wikipedia.org/wiki/User_Datagram_Protocol). - -Unlike TCP, UDP does not check that the message correctly arrived to the server. -It doesn't even establish a connection. That means that UDP is extremely fast: -the data is sent over the network and the code moves on to the next line. -When actually sending data, the overhead of that ping takes about 150 micro-seconds. - -However, this function actually sends data every 100 invocation, because we don't -need to measure *all* invocations. We only need an approximation. -That means that 99% of the time, no data is sent, and the function takes 30 micro-seconds. -If we average all executions, the overhead of that ping is about 31 micro-seconds. -Given that it is much much less than even 1 milli-second, we consider that overhead negligible. - -#### Disabling - -The ping can be disabled by setting a `BREF_PING_DISABLE` environment variable to `1`. diff --git a/docs/runtimes/_meta.json b/docs/runtimes/_meta.json new file mode 100644 index 000000000..f9b80dd57 --- /dev/null +++ b/docs/runtimes/_meta.json @@ -0,0 +1,6 @@ +{ + "fpm-runtime": "PHP-FPM runtime", + "function": "Function runtime", + "console": "Console runtime", + "runtimes-details": "Runtimes in details" +} \ No newline at end of file diff --git a/docs/runtimes/console.md b/docs/runtimes/console.mdx similarity index 53% rename from docs/runtimes/console.md rename to docs/runtimes/console.mdx index ff205ce5a..1a9abbf8e 100644 --- a/docs/runtimes/console.md +++ b/docs/runtimes/console.mdx @@ -1,24 +1,30 @@ ---- -title: Console commands -current_menu: console-applications -introduction: Learn how to run serverless console commands on AWS Lambda with Symfony Console or Laravel Artisan. -previous: - link: /docs/websites.html - title: Website assets -next: - link: /docs/web-apps/cron.html - title: Cron commands ---- +import { NextSeo } from 'next-seo'; -Bref provides a way to run console commands on AWS Lambda. + -This can be used to run PHP scripts, the [Symfony Console](https://symfony.com/doc/current/console.html), as well as [Laravel Artisan](https://laravel.com/docs/artisan) commands in production. +# Console runtime -## Configuration +Bref's "Console" runtime lets us run CLI scripts on AWS Lambda. -The lambda function used for running console applications must use the `php-xx-console` runtime. Here is an example `serverless.yml`: +This can be used to run PHP scripts, like cron tasks, the [Symfony Console](https://symfony.com/doc/current/console.html), [Laravel Artisan](https://laravel.com/docs/artisan), and more. -```yaml +## How it works + +When the function is invoked, the Console runtime will execute the PHP script defined as the *handler* in a sub-process. + +The result of the execution (exit code and output) will be returned as the result of the AWS Lambda invocation. All the CLI output is also logged ([learn more about logs](../environment/logs.mdx)). + +Console functions can be invoked: + +- via a cron schedule +- via the `serverless bref:cli` command +- manually by invoking the function via the AWS API + +## Usage + +The Lambda function used for running console applications must use the `php-xx-console` runtime. Here is an example: + +```yml filename="serverless.yml" {8-9} service: app provider: name: aws @@ -26,15 +32,44 @@ plugins: - ./vendor/bref/bref functions: hello: - handler: bin/console # or 'artisan' for Laravel + handler: the-php-script-to-run.php runtime: php-81-console ``` -Behind the scenes, the `php-xx-console` runtime will deploy a Lambda function configured to use Bref's `php-81` AWS Lambda layer plus Bref's `console` layer (read more about these in the [runtimes documentation](./README.md)). +Behind the scenes, the `php-xx-console` runtime will deploy a Lambda function configured to use Bref's `php-81` AWS Lambda layer plus Bref's `console` layer (read more about these in the [runtimes documentation](../runtimes.mdx)). -## Usage +## Running commands + +When invoked, the "Console" runtime executes the `handler` script in a sub-process. For example, if the following handler was defined: + +```yml filename="serverless.yml" +functions: + hello: + handler: the-php-script-to-run.php + runtime: php-81-console +``` + +Then the following command would run in Lambda every time the function is invoked: + +```sh +php the-php-script-to-run.php +``` -To run a console command on AWS Lambda, run `serverless bref:cli` on your computer: +The Lambda function can be invoked with a payload. It must be a JSON string, for example `"arg1 arg2 --option1=foo"`. Note that it is a string encoded in JSON, that is why it is in quotes, `json_decode($payload)` would return the string itself. + +In our example, the following command would run in Lambda when invoked with such a payload: + +```sh +php the-php-script-to-run.php arg1 arg2 --option1=foo +``` + +### Cron + +Read the dedicated documentation for [running cron tasks on AWS Lambda](../use-cases/cron.mdx). + +### CLI invocation + +To manually run a console command on AWS Lambda, run `serverless bref:cli` on your computer: ```bash serverless bref:cli --args="" @@ -64,7 +99,17 @@ $ AWS_ACCESS_KEY_ID=foo AWS_SECRET_ACCESS_KEY=bar serverless bref:cli # ... ``` -### Usage without Serverless Framework +The `bref:cli` command can be used to run CLI commands in Lambda from your machine, but can also be used in CI/CD to run DB migrations for example. + +### Interactive terminal + +As an alternative to the CLI, the [Bref Dashboard](https://dashboard.bref.sh/?ref=bref) provides a convenient way to run commands via a terminal: + +[![Bref Dashboard terminal for Laravel](../runtimes/dashboard-terminal.png)](https://dashboard.bref.sh/?ref=bref) + +Functions using the "console" runtime are automatically detected, and colors are enabled by default for Laravel Artisan and Symfony Console. + +### Without Serverless Framework If you do not use `serverless.yml` but something else, like SAM/AWS CDK/Terraform, you can invoke your console function via the AWS CLI. For example: @@ -97,12 +142,6 @@ aws lambda invoke \ > **Note:** > The `--payload` needs to contain a JSON string, that is why it is quoted twice: `'"..."'`. This is intentional. -The [Bref Dashboard](https://dashboard.bref.sh/?ref=bref) also provides a convenient way to run commands via a terminal: - -[![Bref Dashboard terminal for Laravel](./dashboard-terminal.png)](https://dashboard.bref.sh/?ref=bref) - -Functions using the "console" runtime are automatically detected, and colors are enabled by default for Laravel Artisan and Symfony Console. - ## Lambda context Lambda provides information about the invocation, function, and execution environment via the *lambda context*. diff --git a/docs/runtimes/fpm-runtime.mdx b/docs/runtimes/fpm-runtime.mdx new file mode 100644 index 000000000..9765f30b7 --- /dev/null +++ b/docs/runtimes/fpm-runtime.mdx @@ -0,0 +1,83 @@ +import { Callout } from 'nextra/components'; +import { NextSeo } from 'next-seo'; + + + +# PHP-FPM runtime for AWS Lambda + +To run HTTP APIs and websites on AWS Lambda, Bref runs your code **using PHP-FPM**. That means PHP applications can run on Lambda just like on any other PHP hosting platform. + +That's great: we can use our favorite framework as usual, like **Laravel or Symfony**. + + + Every code we deploy on AWS Lambda is called a "Function". Do not let this name confuse you: we do deploy HTTP **applications** in a Lambda Function. + + In the Lambda world, an HTTP application is a *function* that is called by a request and returns a response. Good news: this is exactly what our PHP applications do. + + +## How it works + +AWS Lambda can react to HTTP requests via [API Gateway](https://aws.amazon.com/api-gateway/). On Lambda, the Bref runtime starts PHP-FPM and forwards API Gateway requests to PHP-FPM via the FastCGI protocol. + +![](./fpm-runtime.png) + +Bref is basically doing the same thing as Apache or Nginx, and the PHP code runs in the same environment as on any server. + +While this may sound like a lot to deploy an entire application inside Lambda, it works very well. Performance-wise, the overhead of the Bref runtime with PHP-FPM is less than a millisecond. Many users and companies have been running websites and HTTP APIs on Lambda at scale for years. + +## Usage + +To deploy an HTTP application on AWS Lambda, use the `php-xx-fpm` runtime and combine it with the `httpApi` event: + +```yml filename="serverless.yml" {9-11} +service: app +provider: + name: aws +plugins: + - ./vendor/bref/bref +functions: + app: + handler: index.php + runtime: php-81-fpm + events: + - httpApi: '*' +``` + +The `httpApi` event will deploy an API Gateway HTTP API and the `*` configuration will forward all requests to your PHP application. + +### Handler + +The *handler* is the file that is invoked when an HTTP request comes in. + +It is the same file that is traditionally configured in Apache or Nginx. In Symfony and Laravel this is usually `public/index.php` but it can be anything. + +```yml filename="serverless.yml" +functions: + app: + handler: public/index.php +``` + +## Context access + +### Lambda context + +Lambda provides information about the invocation, function, and execution environment via the *lambda context*. + +Bref exposes the Lambda context in the `$_SERVER['LAMBDA_INVOCATION_CONTEXT']` variable as a JSON-encoded string. +Here is an example to retrieve it: + +```php +$lambdaContext = json_decode($_SERVER['LAMBDA_INVOCATION_CONTEXT'], true); +``` + +### Request context + +API Gateway integrations can add information to the HTTP request via the *request context*. +This is the case, for example, when using AWS Cognito authentication on API Gateway. + +Bref exposes the request context in the `$_SERVER['LAMBDA_REQUEST_CONTEXT']` variable as a JSON-encoded string. +Here is an example to retrieve it: + +```php +$requestContext = json_decode($_SERVER['LAMBDA_REQUEST_CONTEXT'], true); +``` diff --git a/docs/runtimes/fpm-runtime.png b/docs/runtimes/fpm-runtime.png new file mode 100644 index 000000000..9a87cfe50 Binary files /dev/null and b/docs/runtimes/fpm-runtime.png differ diff --git a/docs/runtimes/function.md b/docs/runtimes/function.md deleted file mode 100644 index 627a74d34..000000000 --- a/docs/runtimes/function.md +++ /dev/null @@ -1,155 +0,0 @@ ---- -title: PHP Lambda functions -current_menu: php-functions -introduction: Run serverless event-driven PHP functions on AWS Lambda using Bref. -next: - link: /docs/function/handlers.html - title: Typed PHP Lambda handlers ---- - -Previously, we saw how to use AWS Lambda as web hosting for complete web applications. -But we can also run event-driven **PHP functions** on AWS Lambda. - -Here is an example of a PHP Lambda function written as an anonymous function: - -```php - If you instead want to write a classic **HTTP application** read [Bref for web apps](/docs/runtimes/http.md). - -### CLI - -A PHP function can be triggered manually from the CLI using the [`serverless invoke` command](https://serverless.com/framework/docs/providers/aws/cli-reference/invoke/): - -```bash -$ serverless invoke -f -# The function name is the one in serverless.yml, in our example that would be `hello`: -$ serverless invoke -f hello -"Hello world" -``` - -To pass event data to the lambda use the `--data` option. For example: - -```bash -serverless invoke -f --data='{"name": "John" }' -``` - -Run `serverless invoke --help` to learn more about the `invoke` command. - -### From PHP applications - -A PHP function can be triggered from another PHP application using the AWS PHP SDK: - -You first need to install the AWS PHP SDK by running - -```bash -$ composer require aws/aws-sdk-php -``` - -```php -$lambda = new \Aws\Lambda\LambdaClient([ - 'version' => 'latest', - 'region' => '', -]); - -$result = $lambda->invoke([ - 'FunctionName' => '', - 'InvocationType' => 'RequestResponse', - 'LogType' => 'None', - 'Payload' => json_encode(/* event data */), -]); - -$result = json_decode($result->get('Payload')->getContents(), true); -``` - -> A lighter alternative to the official AWS PHP SDK is the [AsyncAws Lambda](https://async-aws.com/clients/lambda.html) package. - -### From other AWS services - -Functions are perfect to react to events emitted by other AWS services. - -For example, you can write code that processes new SQS events, SNS messages, new uploaded files on S3, DynamoDb insert and update events, etc. - -This can be achieve by configuring which events will trigger your function via `serverless.yml`. Learn more about this [in the Serverless documentation](https://serverless.com/framework/docs/providers/aws/events/). diff --git a/docs/runtimes/function.mdx b/docs/runtimes/function.mdx new file mode 100644 index 000000000..3f6e73d15 --- /dev/null +++ b/docs/runtimes/function.mdx @@ -0,0 +1,277 @@ +import { Callout, Tab, Tabs } from 'nextra/components'; +import { NextSeo } from 'next-seo'; + + + +# PHP functions runtime for AWS Lambda + +Bref's **"Event-driven function" runtime** lets you run PHP functions on AWS Lambda. + +Unlike the [PHP-FPM runtime](./fpm-runtime.mdx), the function runtime does not use PHP-FPM. Instead, it invokes your PHP code directly with the AWS Lambda event. + +Here is an example of a PHP Lambda function written as an anonymous function: + +```php + + If you are creating HTTP applications, the [PHP-FPM runtime](./fpm-runtime.mdx) is a simpler option. + + +## Usage + +To deploy a PHP function on AWS Lambda, use the `php-xx` runtime: + +```yaml +service: app +provider: + name: aws +plugins: + - ./vendor/bref/bref +functions: + hello: + handler: my-function.php + runtime: php-81 +``` + +## PHP functions + +Functions that can run on Lambda can be an anonymous function or [any kind of callable supported by PHP](https://www.php.net/manual/en/language.types.callable.php). + +```php + + + Set the class name as the `handler` and Bref will retrieve that class from Laravel's service container. + + ```yml filename="serverless.yml" + functions: + hello: + handler: MyApp\Handler + ``` + + + Set the class name as the `handler` and Bref will retrieve that class from Symfony's service container. + + ```yml filename="serverless.yml" + functions: + hello: + handler: MyApp\Handler + ``` + + + To achieve that, you must integrate Bref with your framework's Dependency Injection Container. + + First, create a file (for example `init.php`) that calls `Bref::setContainer()`: + + ```php + + + +## Invocation + +A PHP function must be invoked via the AWS Lambda API, either manually or by integrating with other AWS services. + + + If you instead want to write a classic **HTTP application**, use the [PHP-FPM runtime](./fpm-runtime.mdx) instead. + + +### CLI + +A PHP function can be triggered manually from the CLI using the [`serverless invoke` command](https://serverless.com/framework/docs/providers/aws/cli-reference/invoke/): + +```bash +$ serverless invoke -f +# The function name is the one in serverless.yml, in our example that would be `hello`: +$ serverless invoke -f hello +"Hello world" +``` + +To pass event data to the lambda use the `--data` option. For example: + +```bash +serverless invoke -f --data='{"name": "John" }' +``` + +Run `serverless invoke --help` to learn more about the `invoke` command. + +### From PHP applications + +A PHP function can be triggered from another PHP application using the AWS PHP SDK: + +You first need to install the AWS PHP SDK by running + +```bash +$ composer require aws/aws-sdk-php +``` + +```php +$lambda = new \Aws\Lambda\LambdaClient([ + 'version' => 'latest', + 'region' => '', +]); + +$result = $lambda->invoke([ + 'FunctionName' => '', + 'InvocationType' => 'RequestResponse', + 'LogType' => 'None', + 'Payload' => json_encode(/* event data */), +]); + +$result = json_decode($result->get('Payload')->getContents(), true); +``` + + + A lighter alternative to the official AWS PHP SDK is the [AsyncAws Lambda](https://async-aws.com/clients/lambda.html) package. + + +### From other AWS services + +Functions are perfect to react to events emitted by other AWS services. + +For example, you can write code that processes new SQS events, SNS messages, new uploaded files on S3, DynamoDb insert and update events, etc. + +Plenty of examples are available in the "Use cases" section in the menu, get started there! diff --git a/docs/runtimes/http.md b/docs/runtimes/http.md deleted file mode 100644 index 0258751cf..000000000 --- a/docs/runtimes/http.md +++ /dev/null @@ -1,153 +0,0 @@ ---- -title: Web applications on AWS Lambda -current_menu: web-apps -introduction: Learn how to run serverless HTTP applications with PHP on AWS Lambda using Bref. -previous: - link: /docs/runtimes/ - title: PHP runtimes for AWS Lambda -next: - link: /docs/websites.html - title: Website assets ---- - -To run HTTP APIs and websites on AWS Lambda, Bref runs your code **using PHP-FPM**. That means PHP applications can run on Lambda just like on any other PHP hosting platform. - -That's great: we can use our favorite framework as usual, like **Laravel or Symfony**. - -If you are interested in the details, know that AWS Lambda can react to HTTP requests via [API Gateway](https://aws.amazon.com/api-gateway/). On Lambda, Bref forwards API Gateway requests to PHP-FPM via the FastCGI protocol (just like Apache or Nginx). - -> Every code we deploy on AWS Lambda is called a "Function". Do not let this name confuse you: in this chapter, we do deploy HTTP **applications** in a Lambda Function. -> -> In the Lambda world, an HTTP application is a *function* that is called by a request and returns a response. Good news: this is exactly what our PHP applications do. - -## Setup - -Below is a minimal `serverless.yml` configuration to deploy an HTTP application: - -```yaml -service: app -provider: - name: aws - runtime: provided.al2 -plugins: - - ./vendor/bref/bref -functions: - app: - handler: index.php - runtime: php-81-fpm - events: - - httpApi: '*' -``` - -To create it automatically, run `vendor/bin/bref init` and select "Web application". - -## Handler - -The *handler* is the file that will be invoked when an HTTP request comes in. - -It is the same file that is traditionally configured in Apache or Nginx. In Symfony and Laravel this is usually `public/index.php` but it can be anything. - -```yaml -functions: - app: - handler: public/index.php -``` - -## Runtime - -For web apps, the runtime to use is the **FPM** runtime (`php-81-fpm`): - -```yaml -functions: - app: - runtime: php-81-fpm -``` - -To learn more check out [the runtimes documentation](/docs/runtimes/README.md). - -## Routing - -On AWS Lambda there is no Apache or Nginx. API Gateway acts as the webserver. - -The simplest API Gateway configuration is to send all incoming requests to our application: - -```yaml - events: - - httpApi: '*' -``` - -### Assets - -Lambda and API Gateway are only used for executing code. Serving assets via PHP does not make sense as this would be a waste of resources and money. - -Deploying a website and serving assets (e.g. CSS, JavaScript, images) is covered in [the "Website assets" documentation](/docs/websites.md). - -In some cases however, you will need to serve images (or other assets) via PHP. One example would be if you served generated images via PHP. In those cases, you need to read the [Binary requests and responses](#binary-requests-and-responses) section below. - -## Binary requests and responses - -By default API Gateway **does not support binary HTTP requests or responses** like -images, PDF, binary files… To achieve this, you need to enable the option for binary -media types in `serverless.yml` as well as define the `BREF_BINARY_RESPONSES` environment -variable: - -```yaml -provider: - # ... - apiGateway: - binaryMediaTypes: - - '*/*' - environment: - BREF_BINARY_RESPONSES: '1' -``` - -This will make API Gateway support binary file uploads and downloads, and Bref will -automatically encode responses to base64 (which is what API Gateway now expects). - -Be aware that the max upload and download size is 6MB. -For larger files, use AWS S3. -An example is available in [Serverless Visually Explained](https://serverless-visually-explained.com/). - -## Context access - -### Lambda context - -Lambda provides information about the invocation, function, and execution environment via the *lambda context*. - -Bref exposes the Lambda context in the `$_SERVER['LAMBDA_INVOCATION_CONTEXT']` variable as a JSON-encoded string. -Here is an example to retrieve it: - -```php -$lambdaContext = json_decode($_SERVER['LAMBDA_INVOCATION_CONTEXT'], true); -``` - -### Request context - -API Gateway integrations can add information to the HTTP request via the *request context*. -This is the case, for example, when using AWS Cognito authentication on API Gateway. - -Bref exposes the request context in the `$_SERVER['LAMBDA_REQUEST_CONTEXT']` variable as a JSON-encoded string. -Here is an example to retrieve it: - -```php -$requestContext = json_decode($_SERVER['LAMBDA_REQUEST_CONTEXT'], true); -``` - -## Cold starts - -AWS Lambda automatically destroys Lambda containers that have been unused for 10 minutes. Warming up a new container can take some time, especially if your application is large. This delay is called [cold start](https://mikhail.io/serverless/coldstarts/aws/). - -To mitigate cold starts for HTTP applications, you can periodically send an event to your Lambda including a `{warmer: true}` key. Bref recognizes this event and immediately responds with a `Status: 100` without executing your code. - -You can set up such events using AWS CloudWatch ([read this article for more details](https://www.jeremydaly.com/lambda-warmer-optimize-aws-lambda-function-cold-starts/)): - -```yaml - events: - - httpApi: '*' - - schedule: - rate: rate(5 minutes) - input: - warmer: true -``` - -You can learn more how AWS Lambda scales and runs in the [Serverless Visually Explained](https://serverless-visually-explained.com/) course. diff --git a/docs/runtimes/runtimes-details.mdx b/docs/runtimes/runtimes-details.mdx new file mode 100644 index 000000000..b06e3b3b7 --- /dev/null +++ b/docs/runtimes/runtimes-details.mdx @@ -0,0 +1,138 @@ +import { Callout } from 'nextra/components'; + +# Runtimes in details + + + This section is only useful if you want to learn more, or if you want to use Bref with another deployment tool than Serverless Framework. + + You can skip it for now if you just want to get started with Bref. + + +## AWS Lambda layers + +Bref runtimes are distributed as [AWS Lambda layers](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html). While Bref provides a Serverless plugin to simplify how to use them, you can use the layers directly. + +The layer names (aka "[ARN](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html)") follow this pattern: + +``` +arn:aws:lambda::534081306603:layer:: +``` + +For example: +``` +arn:aws:lambda:us-east-1:534081306603:layer:php-80:21 +``` + +You can use layers via their full ARN, for example in `serverless.yml`: + +```yml filename="serverless.yml" {9} +service: app +provider: + name: aws +functions: + hello: + # ... + runtime: provided.al2 + layers: + - 'arn:aws:lambda:us-east-1:534081306603:layer:php-80:21' +``` + +Or if you are using [SAM's `template.yaml`](https://aws.amazon.com/serverless/sam/): + +```yml filename="template.yml" +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Resources: + Hello: + Type: AWS::Serverless::Function + Properties: + # ... + Runtime: provided.al2 + Layers: + - 'arn:aws:lambda:us-east-1:534081306603:layer:php-80:21' +``` + +Bref layers work with AWS Lambda regardless of the tool you use to deploy your application: Serverless, SAM, CloudFormation, Terraform, AWS CDK, etc. + + + Remember: the layer ARN contains a region. **You need to use the same region as the rest of your application** else Lambda will not find the layer. + + +## Layer versions + +All the layer/runtime versions can be found at [**runtimes.bref.sh**](https://runtimes.bref.sh/). + +Here are the latest versions: + + + +You can also find the appropriate ARN/version for your current Bref version by running: + +```bash +serverless bref:layers +``` + + + If you use a layer ARN directly, you will need to update the ARN regularly (the `version` part). + + +### Layers NPM package + +You can use [the `@bref.sh/layers.js` NPM package](https://github.com/brefphp/layers.js) to get up-to-date layer ARNs in Node applications, for example with the AWS CDK. + +## Telemetry ping + +Bref layers send an anonymous ping to estimate the total number of Lambda invocations powered by Bref. That statistic is useful in two ways: + +- to provide new users an idea on how much Bref is used in production +- to communicate to AWS how much Bref is used and push for better PHP integration with AWS Lambda tooling + +We consider this to be beneficial both to the Bref project (to get more users and more consideration from AWS) and for Bref users (more users means a larger community, a stronger and more active project, as well as more features from AWS). + +So far, knowing the number of Bref invocations has helped Bref grow, be recognized by AWS, and opened a lot of doors. + +### Data + +On the month of the Bref 1.0 release, [Bref was powering 1 billion invocations per month](../../news/01-bref-1.0.md#1-billion-executions-per-month). + +On the month of the Bref 2.0 release, [Bref was powering 10 billion invocations per month](../../news/02-bref-2.0.md). + +### What is sent + +The data sent in the ping is completely anonymous. It does not contain any identifiable data about anything (the project, users, etc.). + +**The only data it contains is:** "A Bref invocation happened with the layer XYZ" (where XYZ is the name of the Bref layer, like "function", "fpm" or "console"). + +Here is an example payload: + +``` +Invocations_100:1|c\nLayer_fpm_100:1|c +``` + +Anyone can inspect the code and the data sent by checking the [`Bref\Runtime\LambdaRuntime::ping()` function](https://github.com/brefphp/bref/blob/master/src/Runtime/LambdaRuntime.php#L374). + +### How is it sent + +The data is sent via the [statsd](https://github.com/statsd/statsd) protocol, over [UDP](https://en.wikipedia.org/wiki/User_Datagram_Protocol). + +Unlike TCP, UDP does not check that the message correctly arrived to the server. +It doesn't even establish a connection. That means that **UDP is extremely fast**: +the data is sent over the network and the code moves on to the next line. +When actually sending data, the overhead of that ping takes about 150 micro-seconds. + +However, this function actually sends data every 100 invocation, because we don't +need to measure *all* invocations. We only need an approximation. +That means that 99% of the time, no data is sent, and the function takes 30 micro-seconds. +If we average all executions, the overhead of that ping is about **31 micro-seconds**. + +Given that it is much much less than even 1 milli-second, we consider that overhead negligible. + +### Disabling + +The ping can be disabled by setting a `BREF_PING_DISABLE` environment variable to `1`. + + + If your company policy requires disabling the ping, it would be extremely beneficial to the project (and transitively to your company) to report privately a rough number of invocations. You can send me a direct email at [matthieu@bref.sh](mailto:matthieu@bref.sh). + + Such data is collected privately and is not shared publicly on its own. I include it in the total number of invocations that I share publicly. + diff --git a/docs/serverless-costs.mdx b/docs/serverless-costs.mdx new file mode 100644 index 000000000..0db87c3a0 --- /dev/null +++ b/docs/serverless-costs.mdx @@ -0,0 +1,304 @@ +import { useState } from 'react'; +import { NextSeo } from 'next-seo'; + + + +# Costs of a serverless application + +Unlike traditional hosting, serverless hosting is billed based on usage. This means that you only pay for what you use, **down to the request**. + +To be clear, this means that if your application is not used, you don't pay anything. + +On AWS Lambda, you pay for: + +- the number of requests +- the duration of the requests (the time it takes to execute your code) + +There are no costs when the PHP application is waiting between requests (or jobs, events, etc.). It doesn't matter if AWS Lambda scaled your functions up to several instances (containers), you only pay for the requests and the duration of the requests. + +For some use cases it has interesting consequences: 1 job running for 10 minutes has about the same costs as 600 jobs running for 1 second in parallel. + +## Costs calculator + +Use the calculator below to estimate the costs of running your PHP application serverless on AWS Lambda. + + + +export function Calculator() { + const lambdaFreeTierRequests = 1000000; + const lambdaFreeTierDuration = 400000; + const lambdaCostPerRequest = 0.2 / 1000000; + const lambdaCostPerGbS = 0.0000133334; // ARM + + const apiGatewayFreeTierRequests = 1000000; + const apiGatewayCostPerRequest = 1 / 1000000; + + const cloudfrontFreeTierRequests = 10000000; + const cloudfrontFreeTierBandwidth = 1000; + const cloudfrontCostPerRequest = 0.012 / 10000; + const cloudfrontCostPerGb = 0.085; + + const s3FreeRequests = 20000; + const s3CostPerRequest = 0.0004 / 1000; + + const sqsFreeTierRequests = 1000000; + const sqsCostPerRequest = 0.4 / 1000000; + + const rdsStorageFreeTier = 20; + const rdsStorageCostPerGb = 0.115; + const rdsInstanceCost = { + 'db.t4g.micro': 0.016, + 'db.t4g.small': 0.032, + 'db.t4g.medium': 0.065, + 'db.t4g.large': 0.129, + 'db.t4g.xlarge': 0.258, + 'db.t4g.2xlarge': 0.517, + }; + const rdsInstanceDescription = { + 'db.t4g.micro': '1 vCPU, 1GB RAM', + 'db.t4g.small': '1 vCPU, 2GB RAM', + 'db.t4g.medium': '2 vCPU, 4GB RAM', + 'db.t4g.large': '2 vCPU, 8GB RAM', + 'db.t4g.xlarge': '4 vCPU, 16GB RAM', + 'db.t4g.2xlarge': '8 vCPU, 32GB RAM', + }; + const natGatewayCost = 0.045 * 24 * 30; + const natInstanceCost = 0.0042 * 24 * 30; + + const [ httpRequests, setHttpRequests ] = useState(100000); + const [ httpDuration, setHttpDuration ] = useState(100); + const [ assetsRequests, setAssetsRequests ] = useState(0); + const [ bandwidth, setBandwidth ] = useState(0); + const [ cacheHit, setCacheHit ] = useState(90); + const [ jobs, setJobs ] = useState(0); + const [ database, setDatabase ] = useState(undefined); + const [ databaseSize, setDatabaseSize ] = useState(20); + const [ natType, setNatType ] = useState('natInstance'); + const [ jobDuration, setJobDuration ] = useState(50); + + // Calculate costs + const lambdaInvocations = Number(httpRequests) + Number(jobs); + const lambdaGbS = httpRequests * httpDuration / 1000 + jobs * jobDuration / 1000; + const lambdaCost = Math.max(0, (lambdaInvocations - lambdaFreeTierRequests) * lambdaCostPerRequest) + + Math.max(0, (lambdaGbS - lambdaFreeTierDuration) * lambdaCostPerGbS); + const apiGatewayCost = Math.max(0, (httpRequests - apiGatewayFreeTierRequests) * apiGatewayCostPerRequest); + const cloudfrontRequestsCost = Math.max(0, (assetsRequests - cloudfrontFreeTierRequests) * cloudfrontCostPerRequest); + const cloudfrontBandwidthCost = Math.max(0, (bandwidth - cloudfrontFreeTierBandwidth) * cloudfrontCostPerGb); + const cloudfrontCost = cloudfrontRequestsCost + cloudfrontBandwidthCost; + const requestsHittingS3 = assetsRequests * cacheHit / 100; + const s3Cost = Math.max(0, (requestsHittingS3 - s3FreeRequests) * s3CostPerRequest); + const numberOfSqsRequestsPerJob = 3; // Send, receive, delete + const sqsCost = Math.max(0, (jobs * numberOfSqsRequestsPerJob - sqsFreeTierRequests) * sqsCostPerRequest); + const rdsCost = database ? rdsInstanceCost[database] * 24 * 30 + Math.max(0, (databaseSize - rdsStorageFreeTier) * rdsStorageCostPerGb) : 0; + const natCost = (database && natType) ? (natType === 'natInstance' ? natInstanceCost : natGatewayCost) : 0; + + const totalCost = lambdaCost + apiGatewayCost + cloudfrontCost + s3Cost + sqsCost + rdsCost + natCost; + + function smallApi() { + setHttpRequests(100000); + setHttpDuration(100); + setAssetsRequests(0); + setBandwidth(0); + } + function largeApi() { + setHttpRequests(5000000); + setHttpDuration(100); + setAssetsRequests(0); + setBandwidth(0); + } + function smallWebsite() { + setHttpRequests(100000); + setHttpDuration(200); + setAssetsRequests(100000); + setBandwidth(2); + setCacheHit(90); + } + function largeWebsite() { + setHttpRequests(5000000); + setHttpDuration(200); + setAssetsRequests(5000000); + setBandwidth(100); + setCacheHit(90); + } + + return ( +
+
+
+ + + + +
+
+ +
+ + + + + + +
+ +
+ + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + {Object.entries(rdsInstanceDescription) + .map(([instance, description]) => )} + + + {database && + + } + {database && + + + + + + } +
+ +
+ + ${lambdaCost.toFixed(2)} + +
+ Assumptions: 1024MB of memory (Bref default config) and ARM CPUs. +
+ + ${apiGatewayCost.toFixed(2)} + +
+ Assumptions: API Gateway v2. Lambda Function URL could be used instead of API Gateway to reduce costs. +
+ {assetsRequests > 0 && + ${cloudfrontCost.toFixed(2)} + } + {assetsRequests > 0 &&
+ CloudFront costs can vary depending on the use case (e.g. number and size of assets, serving images, streaming videos…). + Browser caching can be used to reduce costs on high traffic websites. + Cloudflare can also be considered as an alternative to CloudFront. +
} + {assetsRequests > 0 && + ${s3Cost.toFixed(2)} + } + {assetsRequests > 0 &&
+ S3 is used to store assets. +
} + {jobs > 0 && + ${sqsCost.toFixed(2)} + } + {jobs > 0 &&
+ Assumptions: Standard SQS queue. +
} + {database && + ${rdsCost.toFixed(2)} + } + {database &&
+ Costs vary depending on the use case. Aurora Serverless can be considered, as well as reserved RDS instances for lower costs. + {' '} + PlanetScale can also be considered as an alternative to RDS. +
} + {database && + ${natCost.toFixed(2)} + } + {database &&
+ NAT Gateway is necessary when using a database in a private VPC (virtual network). + Data transfer cost is excluded. +
} +
+ +
+

+ Total AWS costs +

+
+ ${totalCost.toFixed(2)}/month +
+
+
+
+ The calculator takes into account the AWS free tier (except the free tier that expires after 12 months). + It uses prices for the us-east-1 region. Other regions can have slightly higher costs (a few percents usually). +
+
+ Costs can vary at scale and can be optimized in numerous ways. + For a cost-optimized architecture tailored to your needs, get in touch. +
+
+
+ ) +} + +export function Section({ children, title, ...props }) { + return <> +

+ {title} +

+
+ {children} +
+ +} + +export function Field({ children, title, ...props }) { + return
+ +
+ {children} +
+
+} + +export function Button({ children, ...props }) { + return +} + +export function InputField({ children, value, valueSetter, ...props }) { + return valueSetter(e.target.value)}/> +} + +export function SelectField({ children, value, valueSetter, ...props }) { + return ; +} + +export function PlainTextField({ children, ...props }) { + return
+ {children} +
+} diff --git a/docs/setup.mdx b/docs/setup.mdx new file mode 100644 index 000000000..4b4d8ab38 --- /dev/null +++ b/docs/setup.mdx @@ -0,0 +1,71 @@ +import { Callout, Steps, Cards, Card } from 'nextra/components'; +// Path relative to the copy in the `website/` folder +import { LaravelIcon } from '../../components/icons/LaravelIcon'; +import { SymfonyIcon } from '../../components/icons/SymfonyIcon'; +import { NextSeo } from 'next-seo'; + + + +# Setup + +To use Bref, you will need an AWS account and the `serverless` CLI. Let's get started: + + + + ### AWS account + + Bref deploys your applications to your AWS account. To create one, **go to [aws.amazon.com](https://aws.amazon.com/) and click *Sign up***. + + AWS has a generous free tier that will usually allow you to deploy your first serverless applications for free. + + ### Serverless CLI + + Bref relies on the [Serverless framework](https://serverless.com/) and AWS access keys to deploy applications. You will need to install the `serverless` CLI ([more details here](https://serverless.com/framework/docs/providers/aws/guide/quick-start/)): + + ```bash + npm install -g serverless + ``` + + Bref is compatible with Serverless Framework v3 (current version). + + ### AWS credentials + + Finally, we need AWS credentials so that the `serverless` CLI can deploy to AWS. + + + If you have already set up AWS credentials on your machine (for example if you use the `aws` CLI), you can skip this step. + + + - [Create AWS access keys](./setup/aws-keys.mdx) + + - Set up those keys by running: + + ```bash + serverless config credentials --provider aws --key "key" --secret "secret" + ``` + + This will store the credentials in `~/.aws/credentials` (the [official file for AWS credentials](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html)). This is the same as running the `aws configure` command with the `aws` CLI. + + Alternatively (for example in CI/CD), you can store credentials in environment variables: + + ```bash + export AWS_ACCESS_KEY_ID=key + export AWS_SECRET_ACCESS_KEY=secret + ``` + + + +## What's next? + +That's it, you're ready to use Bref! + + + } title="Get started with Laravel" arrow="true" href="/docs/laravel/getting-started" /> + } title="Get started with Symfony" arrow="true" href="/docs/symfony/getting-started" /> + + + + + Bref is compatible with PHP 8.0 or greater. + If you are using PHP 7.4, Bref v1 (previous major version) will be installed instead. + diff --git a/docs/setup/_meta.json b/docs/setup/_meta.json new file mode 100644 index 000000000..22edc2673 --- /dev/null +++ b/docs/setup/_meta.json @@ -0,0 +1,8 @@ +{ + "aws-keys": { + "display": "hidden" + }, + "minimal-aws-policy": { + "display": "hidden" + } +} \ No newline at end of file diff --git a/docs/installation/aws-keys-step-1.png b/docs/setup/aws-keys-step-1.png similarity index 100% rename from docs/installation/aws-keys-step-1.png rename to docs/setup/aws-keys-step-1.png diff --git a/docs/installation/aws-keys-step-2.png b/docs/setup/aws-keys-step-2.png similarity index 100% rename from docs/installation/aws-keys-step-2.png rename to docs/setup/aws-keys-step-2.png diff --git a/docs/installation/aws-keys-step-3.png b/docs/setup/aws-keys-step-3.png similarity index 100% rename from docs/installation/aws-keys-step-3.png rename to docs/setup/aws-keys-step-3.png diff --git a/docs/installation/aws-keys-step-4.png b/docs/setup/aws-keys-step-4.png similarity index 100% rename from docs/installation/aws-keys-step-4.png rename to docs/setup/aws-keys-step-4.png diff --git a/docs/installation/aws-keys-step-5.png b/docs/setup/aws-keys-step-5.png similarity index 100% rename from docs/installation/aws-keys-step-5.png rename to docs/setup/aws-keys-step-5.png diff --git a/docs/installation/aws-keys-step-6.png b/docs/setup/aws-keys-step-6.png similarity index 100% rename from docs/installation/aws-keys-step-6.png rename to docs/setup/aws-keys-step-6.png diff --git a/docs/setup/aws-keys.mdx b/docs/setup/aws-keys.mdx new file mode 100644 index 000000000..0516a18ca --- /dev/null +++ b/docs/setup/aws-keys.mdx @@ -0,0 +1,42 @@ +import { Callout } from 'nextra/components'; +import { NextSeo } from 'next-seo'; + + + +# Creating AWS access keys + +To create AWS access key for Bref and the `serverless` CLI tool follow these steps: + +1. In the AWS dashboard, go into IAM and create a new user: [**click here** for a direct link](https://us-east-1.console.aws.amazon.com/iamv2/home#/users/create). + +1. Set a user name (for example "bref-cli") and move to the next screen. + + ![](./aws-keys-step-1.png) + +1. Click **Attach policies directly**, search for **AdministratorAccess** and select it. + + ![](./aws-keys-step-2.png) + + + The "AdministratorAccess" policy grants full access to your AWS account. This is simpler when starting with AWS and Bref. However, it is recommended to restrict permissions further eventually. Here is an example of an [IAM policy with stricter permissions](./minimal-aws-policy.mdx). + + +1. Finish creating the user. + +1. Once your user is created, select it and go to **Security credentials**. + + ![](./aws-keys-step-3.png) + +1. Scroll down to **Access Keys** and click on **Create access key**. + + ![](./aws-keys-step-4.png) + +1. Then select **Command Line Interface**. + + ![](./aws-keys-step-5.png) + +1. Add a description to your access keys and click on **Create access key**. + + ![](./aws-keys-step-6.png) + +[< Back to the installation guide](../setup.mdx) diff --git a/docs/setup/minimal-aws-policy.mdx b/docs/setup/minimal-aws-policy.mdx new file mode 100644 index 000000000..51e2d9510 --- /dev/null +++ b/docs/setup/minimal-aws-policy.mdx @@ -0,0 +1,125 @@ +# Minimal AWS policy + +This is a minimal AWS policy that allows the user to deploy a serverless application using the Serverless Framework. + +Note that this policy is not exhaustive and may not work for all use cases. It is intended to be used as a starting point for a minimal policy. + +Feel free to submit a PR if you have any suggestions for improvements. + +```json +{ + "Statement": [ + { + "Action": [ + "apigateway:*", + "cloudformation:CancelUpdateStack", + "cloudformation:ContinueUpdateRollback", + "cloudformation:CreateChangeSet", + "cloudformation:CreateStack", + "cloudformation:CreateUploadBucket", + "cloudformation:DeleteStack", + "cloudformation:DeleteChangeSet", + "cloudformation:Describe*", + "cloudformation:EstimateTemplateCost", + "cloudformation:ExecuteChangeSet", + "cloudformation:Get*", + "cloudformation:List*", + "cloudformation:UpdateStack", + "cloudformation:UpdateTerminationProtection", + "cloudformation:ValidateTemplate", + "dynamodb:CreateTable", + "dynamodb:DeleteTable", + "dynamodb:DescribeTable", + "dynamodb:DescribeTimeToLive", + "dynamodb:UpdateTimeToLive", + "ec2:AttachInternetGateway", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CreateInternetGateway", + "ec2:CreateNetworkAcl", + "ec2:CreateNetworkAclEntry", + "ec2:CreateRouteTable", + "ec2:CreateSecurityGroup", + "ec2:CreateSubnet", + "ec2:CreateTags", + "ec2:CreateVpc", + "ec2:DeleteInternetGateway", + "ec2:DeleteNetworkAcl", + "ec2:DeleteNetworkAclEntry", + "ec2:DeleteRouteTable", + "ec2:DeleteSecurityGroup", + "ec2:DeleteSubnet", + "ec2:DeleteVpc", + "ec2:Describe*", + "ec2:DetachInternetGateway", + "ec2:ModifyVpcAttribute", + "events:DeleteRule", + "events:DescribeRule", + "events:ListRuleNamesByTarget", + "events:ListRules", + "events:ListTargetsByRule", + "events:PutRule", + "events:PutTargets", + "events:RemoveTargets", + "iam:AttachRolePolicy", + "iam:CreateRole", + "iam:DeleteRole", + "iam:DeleteRolePolicy", + "iam:DetachRolePolicy", + "iam:GetRole", + "iam:PassRole", + "iam:PutRolePolicy", + "iot:CreateTopicRule", + "iot:DeleteTopicRule", + "iot:DisableTopicRule", + "iot:EnableTopicRule", + "iot:ReplaceTopicRule", + "kinesis:CreateStream", + "kinesis:DeleteStream", + "kinesis:DescribeStream", + "lambda:*", + "logs:CreateLogGroup", + "logs:DeleteLogGroup", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:FilterLogEvents", + "logs:GetLogEvents", + "logs:PutSubscriptionFilter", + "logs:TagResource", + "logs:DeleteSubscriptionFilter", + "s3:CreateBucket", + "s3:DeleteBucket", + "s3:DeleteBucketPolicy", + "s3:DeleteObject", + "s3:DeleteObjectVersion", + "s3:GetObject", + "s3:GetObjectVersion", + "s3:ListAllMyBuckets", + "s3:ListBucket", + "s3:PutBucketNotification", + "s3:PutBucketPolicy", + "s3:PutBucketTagging", + "s3:PutBucketWebsite", + "s3:PutEncryptionConfiguration", + "s3:PutObject", + "sns:CreateTopic", + "sns:DeleteTopic", + "sns:GetSubscriptionAttributes", + "sns:GetTopicAttributes", + "sns:ListSubscriptions", + "sns:ListSubscriptionsByTopic", + "sns:ListTopics", + "sns:SetSubscriptionAttributes", + "sns:SetTopicAttributes", + "sns:Subscribe", + "sns:Unsubscribe", + "states:CreateStateMachine", + "states:DeleteStateMachine", + "states:TagResource" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" +} +``` diff --git a/docs/symfony/_meta.json b/docs/symfony/_meta.json new file mode 100644 index 000000000..90254d01f --- /dev/null +++ b/docs/symfony/_meta.json @@ -0,0 +1,6 @@ +{ + "getting-started": "Getting started", + "messenger": "Symfony Messenger", + "caching": "", + "keep-alive": "Keeping the Kernel alive" +} \ No newline at end of file diff --git a/docs/symfony/caching.mdx b/docs/symfony/caching.mdx new file mode 100644 index 000000000..9f59a5bc5 --- /dev/null +++ b/docs/symfony/caching.mdx @@ -0,0 +1,21 @@ +# Symfony Caching + +As explained in the [Storage documentation](../environment/storage.md), the filesystem is readonly on AWS Lambda except for `/tmp`. + +However, the `/tmp` directory isn't shared across Lambda instances. If you Lambda function scales up or is redeployed, the cache will be empty in new instances. + +If you want the cache to be shared across all Lambda instances, for example if your application caches a lot of data or if you use it for locking mechanisms (like API rate limiting), you can instead use Redis or DynamoDB. + +DynamoDB is the easiest to set up and is "pay per use". Redis is a bit more complex as it requires a VPC and managing instances, but offers slightly faster response times. + +## DynamoDB Cache + +A Symfony bundle is available to use AWS DynamoDB as cache store: [rikudou/psr6-dynamo-db-bundle](https://github.com/RikudouSage/DynamoDbCachePsr6Bundle). Install the bundle with: + +```bash +composer require rikudou/psr6-dynamo-db-bundle +``` + +Thanks to Symfony Flex, the bundle comes pre-configured to run in Lambda. + +Now, you can follow [this section of the documentation](../environment/storage.md#deploying-dynamodb-tables) to deploy your DynamoDB table using the Serverless Framework. diff --git a/docs/symfony/getting-started.mdx b/docs/symfony/getting-started.mdx new file mode 100644 index 000000000..c367685a6 --- /dev/null +++ b/docs/symfony/getting-started.mdx @@ -0,0 +1,196 @@ +import { Callout } from 'nextra/components'; +import { NextSeo } from 'next-seo'; + + + +# Serverless Symfony - Getting started + +This guide helps you run Symfony applications on AWS Lambda using Bref. These instructions are kept up to date to target the latest Symfony version. + + + A demo application is available on GitHub at [github.com/brefphp/examples](https://github.com/brefphp/examples). + + +## Setup + +First, **follow the [Setup guide](../setup.mdx)** to create an AWS account and install the necessary tools. + +Next, in an existing Symfony project, install Bref and the [Symfony Bridge package](https://github.com/brefphp/symfony-bridge). + +```bash +composer require bref/bref bref/symfony-bridge --update-with-dependencies +``` + +If you are using [Symfony Flex](https://flex.symfony.com/), it will automatically run the [bref/symfony-bridge recipe](https://github.com/symfony/recipes-contrib/tree/master/bref/symfony-bridge/0.1) which will perform the following tasks: + +- Create a `serverless.yml` configuration file optimized for Symfony. +- Add the `.serverless` folder to the `.gitignore` file. + +Otherwise, you can create the `serverless.yml` file manually at the root of the project. Take a look at the [default configuration](https://github.com/symfony/recipes-contrib/blob/master/bref/symfony-bridge/0.1/serverless.yaml) provided by the recipe. + +You still have a few modifications to do on the application to make it compatible with AWS Lambda. + +Since [the filesystem is readonly](/docs/environment/storage.md) except for `/tmp` we need to customize where the cache and logs are stored in +the `src/Kernel.php` file. This is automatically done by the bridge, you just need to use the `BrefKernel` class instead of the default `BaseKernel`: + +```diff filename="src/Kernel.php" +namespace App; + ++ use Bref\SymfonyBridge\BrefKernel; +use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\DependencyInjection\ContainerBuilder; +-use Symfony\Component\HttpKernel\Kernel as BaseKernel; +use Symfony\Component\Routing\RouteCollectionBuilder; + +- class Kernel extends BaseKernel ++ class Kernel extends BrefKernel +{ + // ... +``` + +## Deployment + +Let's deploy the application to AWS Lambda: + +```bash +serverless deploy +``` + +When finished, the `deploy` command will show the URL of the application. + +### Deploying for production + +At the moment, we deployed our local codebase to Lambda. When deploying for production, we probably don't want to deploy: + +- development dependencies, +- our local `.env` files, +- or any other dev artifact. + +Follow [the deployment guide](/docs/deploy.md#deploying-for-production) for more details. + +Separately, on cold starts, Symfony will boot with an empty cache directory. It will build the cache on the fly, which can take a few seconds depending on the complexity of the application. + +To optimize cold starts, you can deploy the application with a warm cache. In a simple application it means that the deployment script should include `cache:warmup` to look something like this: + +```bash +# Install dependencies +composer install --classmap-authoritative --no-dev --no-scripts + +# Warmup the cache +bin/console cache:clear --env=prod + +# Disable use of Dotenv component +echo " .env.local.php + +serverless deploy +``` + +#### Optimizing caches + +When running Symfony on Lambda you should avoid writing to the filesystem. If you pre-warm the cache before deploying you are mostly fine. But you should also make sure you never write to a filesystem cache like `cache.system` or use a pool like: + +```yaml +framework: + cache: + pools: + my_pool: + adapter: cache.adapter.filesystem +``` + +If you don't write to such cache pool you can optimize your setup by not copying the `var/cache/pools` directory. The change below will make sure to symlink the `pools` directory. + +```diff filename="src/Kernel.php" +class Kernel extends BrefKernel +{ + // ... + ++ protected function getWritableCacheDirectories(): array ++ { ++ return []; ++ } +} +``` + +## Troubleshooting + +In case your application is showing a blank page after being deployed, [have a look at the logs](../environment/logs.md). + +## Website assets + +Have a look at the [Website guide](../use-cases/websites.mdx) to learn how to deploy a website with assets. + +## Symfony Console + +As you may have noticed, we define a function named "console" in `serverless.yml`. That function is using the [Console runtime](../runtimes/console.mdx), which lets us run the Symfony Console on AWS Lambda. + +For example, to execute an `bin/console` command on Lambda, run the command below: + +```bash +serverless bref:cli --args="" +``` + +For example: + +```bash +serverless bref:cli --args="doctrine:migrations:migrate" +``` + +For more details follow [the "Console" guide](../runtimes/console.mdx). + +## Logs + +By default, Symfony logs to `stderr`. That is great because Bref [automatically forwards `stderr` to AWS CloudWatch](/docs/environment/logs.md). + +However, if your application is using Monolog, you need to configure it to log to `stderr` as well: + +```yml filename="config/packages/prod/monolog.yaml" {6} +monolog: + handlers: + # ... + nested: + type: stream + path: php://stderr +``` + +## Trust API Gateway + +When hosting a website on Lambda, API Gateway acts as a proxy between the client and your Lambda function. + +By default, Symfony doesn't trust proxies for security reasons, but it's safe to do it when using API Gateway and Lambda. This is needed because otherwise, Symfony will not be able to generate URLs properly. + +Add the following lines to `config/packages/framework.yaml`: + +```yml filename="config/packages/framework.yaml" {2-5} +framework: + # trust the remote address because API Gateway has no fixed IP or CIDR range that we can target + trusted_proxies: '127.0.0.1' + # trust "X-Forwarded-*" headers coming from API Gateway + trusted_headers: [ 'x-forwarded-for', 'x-forwarded-proto', 'x-forwarded-port' ] +``` + +Note that API Gateway doesn't set the `X-Forwarded-Host` header, so we don't trust it by default. You should only whitelist this header if you set it manually, +for example in your CloudFront configuration (this is done automatically +in [the Cloudfront distribution deployed by Lift](../use-cases/websites.mdx)). + +You can get more details in the [Symfony documentation](https://symfony.com/doc/current/deployment/proxies.html). + + + Be careful with these settings if your app is also executed outside a Lambda environment (for example on a public server). + + +### Getting the user IP + +**When using CloudFront** on top of API Gateway, you will not be able to retrieve the client IP address, and you will instead get one of CloudFront's IP when +calling `Request::getClientIp()`. If you really need this, you will need to +whitelist [every CloudFront IP](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/LocationsOfEdgeServers.html) +in `trusted_proxies`. + +## The `kernel.terminate` event + +The [`kernel.terminate` event](https://symfony.com/doc/current/components/http_kernel.html#component-http-kernel-kernel-terminate) runs **synchronously** on Lambda. + +That means that if you use this event, its listeners will be executed **before** the Lambda function returns its response. That will add latency to your response. + +To run asynchronous tasks, use the [Symfony Messenger](./messenger.mdx) instead. diff --git a/docs/symfony/keep-alive.mdx b/docs/symfony/keep-alive.mdx new file mode 100644 index 000000000..0de6636b2 --- /dev/null +++ b/docs/symfony/keep-alive.mdx @@ -0,0 +1,60 @@ +import { Callout } from 'nextra/components'; + +# Keeping the Symfony Kernel alive between requests + + + This is an **advanced approach** aimed at optimizing performance. If you are just starting with Bref, this approach is not recommended because it is more complex to set up and can lead to unexpected issues. + + +By default, Bref uses PHP-FPM to handle HTTP requests. This means that the Symfony Kernel is restarted for every request. This is not a problem for most applications, but if you want to optimize performance, you can keep the Symfony Kernel alive between requests. This approach is similar to [Laravel Octane](../laravel/octane), or running Symfony with [RoadRunner](https://roadrunner.dev/). + +## Usage + +The Bref Symfony Bridge integrates with the Symfony Runtime component. This means that Bref can natively set the Symfony Kernel as the handler for Lambda functions, without going through PHP-FPM: + +```diff filename="serverless.yml" +functions: + app: +- handler: public/index.php ++ handler: App\Kernel + layers: + # Switch from PHP-FPM to the "function" runtime: +- - ${bref:layer.php-81-fpm} ++ - ${bref:layer.php-81} + environment: ++ # The Symfony process will restart every 100 requests ++ BREF_LOOP_MAX: 100 +``` + +The `App\Kernel` will be retrieved via Symfony Runtime from `public/index.php`. If you don't have a `public/index.php`, read the next sections. + +## How it works + +Traditionally, Bref runs Symfony applications with the [PHP-FPM runtime](../runtimes/fpm-runtime.mdx). By switching to the [Function runtime](../runtimes/function.mdx), Bref loads the Symfony Kernel directly and can keep it alive between requests (controlled by `BREF_LOOP_MAX`). + +Note that the execution model of AWS Lambda is unchanged: the entire Lambda instance is frozen between requests. The Symfony Kernel is kept alive in memory, but it is not running. When a new request comes in, the Lambda instance is thawed and the request is handled. + +The main risks with this approach are memory leaks and global state. If your application has memory leaks, the memory usage will increase over time and eventually reach the Lambda limit. This can be mitigated by setting `BREF_LOOP_MAX` to a low value, so that the Symfony Kernel is restarted regularly. If your application uses global state, it will be shared between requests, which can be a disaster security-wise. + +## Custom bootstrap file + +If you do not have a `public/index.php` file, you can create a file that returns the kernel (or any PSR-11 container): + +```php + + +# Symfony Messenger + +Symfony Messenger messages can be dispatched to **SQS, SNS, or EventBridge**, while workers handle those messages on AWS Lambda. + +## Installation + +This guide assumes that: + +- Symfony and [Symfony Messenger are installed](https://symfony.com/doc/current/messenger.html#installation) +- Bref is [installed and set up with Symfony](./getting-started.mdx) + +First, install the [Bref-Symfony messenger integration](https://github.com/brefphp/symfony-messenger): + +```bash +composer require bref/symfony-messenger +``` + +Next, register the bundle in `config/bundles.php`: + +```php filename="config/bundles.php" {3} +return [ + // ... + Bref\Symfony\Messenger\BrefMessengerBundle::class => ['all' => true], +]; +``` + +SQS, SNS, and EventBridge can now be used with Symfony Messenger. + +## Usage + +Symfony Messenger dispatches messages. To create a message, follow the [Symfony Messenger documentation](https://symfony.com/doc/current/messenger.html#creating-a-message-handler). + +To configure **where** messages are dispatched, all the examples in this documentation are based on [the example from the Symfony documentation](https://symfony.com/doc/current/messenger.html#transports-async-queued-messages): + +```yml filename="config/packages/messenger.yaml" +framework: + messenger: + transports: + async: '%env(MESSENGER_TRANSPORT_DSN)%' + routing: + 'App\Message\MyMessage': async +``` + +## SQS + +The [SQS](https://aws.amazon.com/sqs/) service is a queue that is similar to RabbitMQ. To use it, set its URL in the environment variable `MESSENGER_TRANSPORT_DSN`: + +```yml filename="serverless.yml" {4} +provider: + name: aws + environment: + MESSENGER_TRANSPORT_DSN: https://sqs.us-east-1.amazonaws.com/123456789/my-queue +``` + +The implementation uses the SQS transport provided by [Symfony Amazon SQS Messenger](https://symfony.com/doc/current/messenger.html#amazon-sqs), so all its features are supported. If you already use that transport, the transition to AWS Lambda should not require any change for dispatching messages. + +However, instead of creating the SQS queue and the worker manually, you can use the [Serverless Lift](https://github.com/getlift/lift) plugin. + +First install the Lift plugin: + +```bash +serverless plugin install -n serverless-lift +``` + +Then use [the Queue construct](https://github.com/getlift/lift/blob/master/docs/queue.md) in `serverless.yml` to create a queue and a worker: + +```yml filename="serverless.yml" +provider: + # ... + environment: + # ... + MESSENGER_TRANSPORT_DSN: ${construct:jobs.queueUrl} + +functions: + # ... + +constructs: + jobs: + type: queue + worker: + handler: bin/consumer.php + runtime: php-81 + timeout: 60 # in seconds +``` + +You will want to disable `auto_setup` to avoid extra SQS requests and permission issues. + +```yml filename="config/packages/messenger.yaml" {6-7} +framework: + messenger: + transports: + async: + dsn: '%env(MESSENGER_TRANSPORT_DSN)%' + options: + auto_setup: false +``` + +With that configuration, anytime a message is pushed to Symfony Messenger, it will be sent to SQS, and SQS will invoke our "worker" Lambda function so that it is processed. + + + With Lift, AWS credentials (`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`) are automatically set up with the appropriate permissions for Messenger to use the SQS queue. + + +We now need to create the handler script (`bin/consumer.php`): + +```php filename="bin/consumer.php" +bootEnv(dirname(__DIR__).'/.env'); + +$kernel = new \App\Kernel($_SERVER['APP_ENV'], (bool)$_SERVER['APP_DEBUG']); +$kernel->boot(); + +// Return the Bref consumer service +return $kernel->getContainer()->get(SqsConsumer::class); +``` + +Finally, register and configure the `SqsConsumer` service: + +```yml filename="config/services.yaml" +services: + Bref\Symfony\Messenger\Service\Sqs\SqsConsumer: + public: true + autowire: true + arguments: + # Pass the transport name used in config/packages/messenger.yaml + $transportName: 'async' + $partialBatchFailure: true +``` + +### Error handling + +AWS Lambda has error handling mechanisms (retrying and handling failed messages). Because of that, this package does not integrate Symfony Messenger's retry mechanism. Instead, it works with Lambda's retry mechanism. + +With the default Lift configuration, failed messages will be retried 3 times. You can configure this, [learn more](https://github.com/getlift/lift/blob/master/docs/queue.md#retries). + +When using SNS and EventBridge, messages will be retried by default 2 times. + +### FIFO queue + +[FIFO queues](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html) guarantee +exactly once delivery, and have a mandatory queue name suffix `.fifo`. + +With Lift, [set `fifo: true`](https://github.com/getlift/lift/blob/master/docs/queue.md#fifo-first-in-first-out) to enable it: + +```yml filename="serverless.yml" {4} +constructs: + my-queue: + # ... + fifo: true +``` + +[Symfony Amazon SQS Messenger](https://symfony.com/doc/current/messenger.html#amazon-sqs) will automatically calculate/set +the `MessageGroupId` and `MessageDeduplicationId` parameters required for FIFO queues, but you can set them explicitly: + +```php +use Symfony\Component\Messenger\MessageBus; +use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsFifoStamp; + +/* @var MessageBus $messageBus */ +$messageBus->dispatch(new MyAsyncMessage(), [ + new AmazonSqsFifoStamp('my-group-message-id', 'my-deduplication-id'), +]); +``` +Everything else is identical to the normal SQS queue. + +## SNS + +AWS [SNS](https://aws.amazon.com/sns) is "notification" instead of "queues". Messages may not arrive in the same order as sent, and they might arrive all at once. To use it, create an SNS topic and set it as the DSN: + +```dotenv +MESSENGER_TRANSPORT_DSN=sns://arn:aws:sns:us-east-1:1234567890:foobar +``` + +That's it, messages will be dispatched to that topic. + + + When running Symfony on AWS Lambda, it is not necessary to configure credentials. The AWS client will read them [from environment variables](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime) automatically. + + +To consume messages from SNS: + +1. Create the function that will be invoked by SNS in `serverless.yml`: + +```yml filename="serverless.yml" +functions: + worker: + handler: bin/consumer.php + timeout: 20 # in seconds + runtime: php-81 + events: + # Read more at https://www.serverless.com/framework/docs/providers/aws/events/sns/ + - sns: + arn: arn:aws:sns:us-east-1:1234567890:my_sns_topic +``` + +2. Create the handler script (for example `bin/consumer.php`): + +```php filename="bin/consumer.php" +bootEnv(dirname(__DIR__).'/.env'); + +$kernel = new \App\Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); +$kernel->boot(); + +// Return the Bref consumer service +return $kernel->getContainer()->get(SnsConsumer::class); +``` + +3. Register and configure the `SnsConsumer` service: + +```yml filename="config/services.yaml" +services: + Bref\Symfony\Messenger\Service\Sns\SnsConsumer: + public: true + autowire: true + arguments: + # Pass the transport name used in config/packages/messenger.yaml + $transportName: 'async' +``` + +Now, anytime a message is dispatched to SNS, the Lambda function will be called. The Bref consumer class will put back the message into Symfony Messenger to be processed. + +### Error handling + +AWS Lambda has error handling mechanisms (retrying and handling failed messages). Because of that, this package does not integrate Symfony Messenger's retry mechanism. Instead, it works with Lambda's retry mechanism. + +By default, Lambda will retry failed messages 2 times. + +## EventBridge + +AWS [EventBridge](https://aws.amazon.com/eventbridge/) is a message routing service. It is similar to SNS, but more powerful for communication between microservices. + +To use it, configure the DSN like so: + +```dotenv +# "myapp" is the EventBridge "source", i.e. a namespace for your application's messages +# This source name will be reused in `serverless.yml` later. +MESSENGER_TRANSPORT_DSN=eventbridge://myapp +``` +Optionally you can add set the [EventBusName](https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutEventsRequestEntry.html#eventbridge-Type-PutEventsRequestEntry-EventBusName) via a `event_bus_name` query parameter, either the name or the ARN: + +```dotenv +MESSENGER_TRANSPORT_DSN=eventbridge://myapp?event_bus_name=custom-bus +MESSENGER_TRANSPORT_DSN=eventbridge://myapp?event_bus_name=arn:aws:events:us-east-1:123456780912:event-bus/custom-bus +``` + +That's it, messages will be dispatched to EventBridge. + + + When running Symfony on AWS Lambda, it is not necessary to configure credentials. The AWS client will read them [from environment variables](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime) automatically. + + +To consume messages from EventBridge: + +1. Create the function that will be invoked by EventBridge in `serverless.yml`: + +```yml filename="serverless.yml" +functions: + worker: + handler: bin/consumer.php + timeout: 20 # in seconds + runtime: php-81 + events: + # Read more at https://www.serverless.com/framework/docs/providers/aws/events/event-bridge/ + - eventBridge: + # In case of you change bus name in config/packages/messenger.yaml (i.e eventbridge://myapp?event_bus_name=custom-bus) you need to set bus name like below + # eventBus: custom-bus + # This filters events we listen to: only events from the "myapp" source. + # This should be the same source defined in config/packages/messenger.yaml + pattern: + source: + - myapp +``` + +2. Create the handler script (for example `bin/consumer.php`): + +```php filename="bin/consumer.php" +bootEnv(dirname(__DIR__).'/.env'); + +$kernel = new \App\Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); +$kernel->boot(); + +// Return the Bref consumer service +return $kernel->getContainer()->get(EventBridgeConsumer::class); +``` + +3. Register and configure the `EventBridgeConsumer` service: + +```yml filename="config/services.yaml" +services: + Bref\Symfony\Messenger\Service\EventBridge\EventBridgeConsumer: + public: true + autowire: true + arguments: + # Pass the transport name used in config/packages/messenger.yaml + $transportName: 'async' + # Optionnally, if you have different buses in config/packages/messenger.yaml, set $bus like below: + # $bus: '@event.bus' +``` + +Now, anytime a message is dispatched to EventBridge for that source, the Lambda function will be called. The Bref consumer class will put back the message into Symfony Messenger to be processed. + +### Error handling + +AWS Lambda has error handling mechanisms (retrying and handling failed messages). Because of that, this package does not integrate Symfony Messenger's retry mechanism. Instead, it works with Lambda's retry mechanism. + +By default, Lambda will retry failed messages 2 times. + +## Configuration + +### Configuring AWS clients + +By default, AWS clients (SQS, SNS, EventBridge) are preconfigured to work on AWS Lambda (thanks to [environment variables populated by AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime)). + +However, it is possible customize the AWS clients, for example to use them outside of AWS Lambda (locally, on EC2…) or to mock them in tests. These clients are registered as Symfony services under the keys: + +- `bref.messenger.sqs_client` +- `bref.messenger.sns_client` +- `bref.messenger.eventbridge_client` + +For example to customize the SQS client: + +```yml +services: + bref.messenger.sqs_client: + class: AsyncAws\Sqs\SqsClient + public: true # the AWS clients must be public + arguments: + # Apply your own config here + - + region: us-east-1 +``` + +### Disabling transports + +By default, this package registers Symfony Messenger transports for SQS, SNS and EventBridge. + +If you want to disable some transports (for example in case of conflict), you can remove `BrefMessengerBundle` from `config/bundles.php` and reconfigure the transports you want in your application's config. Take a look at [`Resources/config/services.yaml`](https://github.com/brefphp/symfony-messenger/blob/master/src/Resources/config/services.yaml) to copy the part that you want. + +### Customizing the serializer + +If you want to change how messages are serialized, for example to use [Happyr message serializer](https://github.com/Happyr/message-serializer), you need to add the serializer on both the transport and the consumer. For example: + +```yaml +# config/packages/messenger.yaml +framework: + messenger: + transports: + async: + dsn: 'https://sqs.us-east-1.amazonaws.com/123456789/my-queue' + serializer: 'Happyr\MessageSerializer\Serializer' + +# config/services.yaml +services: + Bref\Symfony\Messenger\Service\Sqs\SqsConsumer: + public: true + autowire: true + arguments: + $transportName: 'async' + $serializer: '@Happyr\MessageSerializer\Serializer' +``` diff --git a/docs/upgrading/_meta.json b/docs/upgrading/_meta.json new file mode 100644 index 000000000..1a03b074b --- /dev/null +++ b/docs/upgrading/_meta.json @@ -0,0 +1,3 @@ +{ + "v2": "From 1.x to 2.0" +} \ No newline at end of file diff --git a/docs/upgrading/v2.md b/docs/upgrading/v2.md index ddc0fe3f5..4921827bb 100644 --- a/docs/upgrading/v2.md +++ b/docs/upgrading/v2.md @@ -1,9 +1,9 @@ --- -title: Upgrading to Bref 2.0 -current_menu: upgrading introduction: Upgrading guide to go from Bref 1.x to Bref 2.0. --- +# Upgrading to Bref 2.0 + ## Updating dependencies ### PHP 8 required @@ -67,7 +67,7 @@ The following commands of `vendor/bin/bref` have changed: serverless bref:cli --args="migrate --force" ``` - No need to provide the function name or the region anymore. Read [the Console documentation](../runtimes/console.md#usage) to learn more. You will also find alternatives if you don't use the `serverless` CLI. + No need to provide the function name or the region anymore. Read [the Console documentation](../runtimes/console.mdx#usage) to learn more. You will also find alternatives if you don't use the `serverless` CLI. - `vendor/bin/bref local` is replaced by the simpler `serverless bref:local`. @@ -81,7 +81,7 @@ The following commands of `vendor/bin/bref` have changed: No need to provide the handler file name anymore, we directly use the function name. The new `serverless bref:local` command has similar arguments as `serverless invoke`. - Read [the Local Development documentation](../function/local-development.md) to learn more. You will also find alternatives if you don't use the `serverless` CLI. + Read [the Local Development documentation](../local-development/event-driven-functions.mdx) to learn more. You will also find alternatives if you don't use the `serverless` CLI. - `vendor/bin/bref layers` is replaced by the simpler `serverless bref:layers`. diff --git a/docs/use-cases/_meta.json b/docs/use-cases/_meta.json new file mode 100644 index 000000000..9382bbc3d --- /dev/null +++ b/docs/use-cases/_meta.json @@ -0,0 +1,15 @@ +{ + "http": "HTTP applications", + "websites": "Websites", + "static-websites": "Static websites", + "cron": "Cron tasks", + "s3": "S3 file processing", + "sqs": "SQS asynchronous tasks", + "eventbridge": "EventBridge event bus", + "websockets": "WebSockets", + "sns": "SNS events", + "dynamodb": "DynamoDB events", + "kinesis": "Kinesis stream processing", + "kafka": "Kafka events", + "custom-architecture": "Custom architecture" +} \ No newline at end of file diff --git a/docs/use-cases/cron.mdx b/docs/use-cases/cron.mdx new file mode 100644 index 000000000..4eda71636 --- /dev/null +++ b/docs/use-cases/cron.mdx @@ -0,0 +1,243 @@ +import { Tab, Tabs } from 'nextra/components'; +import { NextSeo } from 'next-seo'; + + + +# Cron tasks on AWS Lambda + +A Lambda function can be invoked on a schedule using the `schedule` event. This is useful for running cron tasks, such as sending emails or cleaning up data. For example: + +```yml filename="serverless.yml" {5-8} +functions: + cron: + # ... + events: + # the schedule can be defined as a rate + - schedule: rate(1 hour) + # or as a cron expression + - schedule: cron(0 12 * * ? *) +``` + +## CLI commands + +Cron events can be used to run CLI commands with the [Console runtime](../runtimes/console.md). + +In that case, use the `php-xx-console` runtime (for example `php-81-console`). + +This is usually best when coupled with a framework like Laravel or Symfony, or when porting an existing cron task to AWS Lambda. + + + + ```yml filename="serverless.yml" + functions: + # ... + cron: + handler: artisan + runtime: php-81-console + events: + - schedule: + rate: rate(1 hour) + # The command needs to be passed as a JSON string + # (that is why it's quoted twice: '"..."') + input: '"my-command --option=value"' + ``` + + The configuration above will run `php artisan my-command --option=value` every hour in the Lambda function named "cron". + + Note that Laravel already provides a [scheduler](https://laravel.com/docs/scheduling) that can be used instead of the `schedule` event. If you want to use it instead, run the `artisan schedule:run` command every minute: + + ```yml filename="serverless.yml" + functions: + # ... + artisan: + handler: artisan + runtime: php-81-console + events: + - schedule: + rate: rate(1 minute) + input: '"schedule:run"' + ``` + + + ```yml filename="serverless.yml" + functions: + # ... + cron: + handler: bin/console + runtime: php-81-console + events: + - schedule: + rate: rate(1 hour) + # The command needs to be passed as a JSON string + # (that is why it's quoted twice: '"..."') + input: '"my-command --option=value"' + ``` + + The configuration above will run `bin/console my-command --option=value` every hour in the Lambda function named "cron". + + + ```yml filename="serverless.yml" + functions: + # ... + cron: + handler: my-script.php + runtime: php-81-console + events: + - schedule: + rate: rate(1 hour) + ``` + + The configuration above will run `php my-script.php` every hour in the Lambda function named "cron". + + If you need to pass CLI options to the script, use the `input` option: + + ```yml filename="serverless.yml" {9-11} + functions: + # ... + cron: + handler: my-script.php + runtime: php-81-console + events: + - schedule: + rate: rate(1 hour) + # The command needs to be passed as a JSON string + # (that is why it's quoted twice: '"..."') + input: '"my-command --option=value"' + ``` + + The configuration above will run `php my-script.php my-command --option=value` every hour in the Lambda function named "cron". + + + +Read more about the options for the `schedule` event in the [Serverless documentation](https://www.serverless.com/framework/docs/providers/aws/events/schedule/). + +## Cron functions + +On top of running CLI cron tasks with the `php-xx-console` runtime, we can also run **event-driven functions** (using the [PHP function runtime](/docs/runtimes/function.md)) as cron tasks. + + + + ```yml filename="serverless.yml" + functions: + # ... + cron: + handler: App\MyCronHandler + runtime: php-81 + events: + - schedule: + rate: rate(1 hour) + ``` + + The handler can be a class implementing the `Handler` interface: + + ```php + namespace App; + + use Bref\Context\Context; + + class MyCronHandler implements \Bref\Event\Handler + { + public function handle($event, Context $context): void + { + echo 'Hello ' . $event['name'] ?? 'world'; + } + } + ``` + + The configuration above will run `MyCronHandler::handle()` every hour. + + It is possible to provide data inside the `$event` variable via the `input` option: + + ```yml filename="serverless.yml" {6-8} + functions: + cron: + events: + - schedule: + rate: rate(1 hour) + input: + foo: bar + hello: world + ``` + + + ```yml filename="serverless.yml" + functions: + # ... + cron: + handler: App\MyCronHandler + runtime: php-81 + events: + - schedule: + rate: rate(1 hour) + ``` + + The handler can be a class implementing the `Handler` interface: + + ```php + namespace App; + + use Bref\Context\Context; + + class MyCronHandler implements \Bref\Event\Handler + { + public function handle($event, Context $context): void + { + echo 'Hello ' . $event['name'] ?? 'world'; + } + } + ``` + + The configuration above will run `MyCronHandler::handle()` every hour. + + It is possible to provide data inside the `$event` variable via the `input` option: + + ```yml filename="serverless.yml" {6-8} + functions: + cron: + events: + - schedule: + rate: rate(1 hour) + input: + foo: bar + hello: world + ``` + + + ```yml filename="serverless.yml" + functions: + # ... + cron: + handler: function.php + runtime: php-81-console + events: + - schedule: + rate: rate(1 hour) + ``` + + The example above will run the function returned by `function.php` every hour in AWS Lambda. For example: + + ```php + + + +Read more about the options for the `schedule` event in the [Serverless documentation](https://www.serverless.com/framework/docs/providers/aws/events/schedule/). diff --git a/docs/use-cases/custom-architecture.mdx b/docs/use-cases/custom-architecture.mdx new file mode 100644 index 000000000..2f9611a85 --- /dev/null +++ b/docs/use-cases/custom-architecture.mdx @@ -0,0 +1,18 @@ +# Custom AWS architecture + +The use cases detailed in the menu are just examples to get started. + +It is possible to combine AWS services to create a custom architecture that fits your needs. + +Here are some examples we've seen over the years: + +- Connect API Gateway straight to SQS to create an infinitely scalable asynchronous endpoint +- Use CloudFront Functions to process incoming requests and route them to different origins +- Use API Gateway API keys and quotas to sell access to your API +- Use CloudFront, S3, and Lambda to automatically resize images on the fly +- Subscribe Lambda to CloudWatch Logs to process logs in real-time +- Switch to ALB or CloudFront to optimize costs +- Generate PDFs on the fly with Lambda and Puppeteer +- And many more... + +If you want to work with experienced AWS architects to design or optimize your architecture, [get in touch](mailto:matthieu@bref.sh). diff --git a/docs/use-cases/dynamodb.mdx b/docs/use-cases/dynamodb.mdx new file mode 100644 index 000000000..568374194 --- /dev/null +++ b/docs/use-cases/dynamodb.mdx @@ -0,0 +1,25 @@ +# DynamoDB events + +To handle [DynamoDB events](https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html), extend the `DynamoDbHandler` class: + +```php +use Bref\Context\Context; +use Bref\Event\DynamoDb\DynamoDbEvent; +use Bref\Event\DynamoDb\DynamoDbHandler; + +class MyHandler extends DynamoDbHandler +{ + public function handleDynamoDb(DynamoDbEvent $event, Context $context): void + { + foreach ($event->getRecords() as $record) { + $keys = $record->getKeys(); + $old = $record->getOldImage(); + $new = $record->getNewImage(); + + // do something + } + } +} +``` + +Learn more about using DynamoDB in `serverless.yml` [in the Serverless Framework documentation](https://www.serverless.com/framework/docs/providers/aws/events/streams/). diff --git a/docs/use-cases/eventbridge.mdx b/docs/use-cases/eventbridge.mdx new file mode 100644 index 000000000..fa6e67f2f --- /dev/null +++ b/docs/use-cases/eventbridge.mdx @@ -0,0 +1,96 @@ +import { Tab, Tabs } from 'nextra/components'; + +# EventBridge event bus + +[EventBridge](https://aws.amazon.com/eventbridge/) is a managed event bus that is perfect for exchanging asynchronous messages between applications and microservices. + +```mermaid +graph LR; +EventBridge(EventBridge):::mermaidAwsColor; +Lambda1(Lambda A):::mermaidAwsColor -->|message| EventBridge; +EventBridge -->|message| Lambda2(Lambda B):::mermaidAwsColor; +EventBridge -->|message| Lambda3(Lambda C):::mermaidAwsColor; +``` + +To handle EventBridge events, extend the `EventBridgeHandler` class: + +```php +use Bref\Context\Context; +use Bref\Event\EventBridge\EventBridgeEvent; +use Bref\Event\EventBridge\EventBridgeHandler; + +class MyHandler extends EventBridgeHandler +{ + public function handleEventBridge(EventBridgeEvent $event, Context $context): void + { + // We can retrieve the message data via `$event->getDetail()` + $message = $event->getDetail(); + + // do something + } +} +``` + +Then, create a Lambda function that listens to EventBridge events with the handler you created: + + + + ```yml filename="serverless.yml" + functions: + # ... + events: + handler: App\MyHandler + events: + - eventBridge: + pattern: + detail-type: + - 'MyCustomEvent' + ``` + + The `App\MyHandler` class will be instantiated by Laravel's service container. + + + ```yml filename="serverless.yml" + functions: + # ... + resizeImage: + handler: App\MyHandler + events: + - eventBridge: + pattern: + detail-type: + - 'MyCustomEvent' + ``` + + The `App\MyHandler` class will be instantiated by Symfony's service container. + + + ```yml filename="serverless.yml" + functions: + # ... + resizeImage: + handler: handler.php + events: + - eventBridge: + pattern: + detail-type: + - 'MyCustomEvent' + ``` + + The file `handler.php` should return the handler instance: + + ```php filename="handler.php" + + + +You can learn more about messaging with EventBridge in [Serverless Visually Explained](https://serverless-visually-explained.com/). + +[![](./eventbridge.png)](https://serverless-visually-explained.com/) + +Learn more about all the options available for EventBridge in `serverless.yml` [in the Serverless Framework documentation](https://www.serverless.com/framework/docs/providers/aws/events/event-bridge/). diff --git a/docs/use-cases/eventbridge.png b/docs/use-cases/eventbridge.png new file mode 100644 index 000000000..752be8fd2 Binary files /dev/null and b/docs/use-cases/eventbridge.png differ diff --git a/docs/use-cases/http.mdx b/docs/use-cases/http.mdx new file mode 100644 index 000000000..cb8320c13 --- /dev/null +++ b/docs/use-cases/http.mdx @@ -0,0 +1,55 @@ +import { Card, Cards } from 'nextra/components'; +// Path relative to the copy in the `website/` folder +import { LaravelIcon } from '../../../components/icons/LaravelIcon'; +import { SymfonyIcon } from '../../../components/icons/SymfonyIcon'; + +# Serverless HTTP applications + +Bref deploy HTTP applications that run with AWS Lambda and [API Gateway](https://aws.amazon.com/api-gateway/): + +```mermaid +graph LR; +START:::mermaidHidden -->|HTTP request| APIGateway(API Gateway):::mermaidAwsColor; +APIGateway -->|invoke| Lambda(Lambda):::mermaidAwsColor; +``` + +On AWS Lambda there is no Apache or Nginx, API Gateway acts as the webserver. Our code is invoked only when there is an HTTP request, and we pay only for the request and the execution time of our code. + +Bref takes care of setting up everything so that your code runs the same way as on a traditional server with Apache or Nginx. + +## Usage + +HTTP applications is the default use case with Bref. That's why there is (almost) no documentation here. + +Instead, head to the **Getting started** guide for your framework: + + + } title="Get started with Laravel" arrow="true" href="/docs/laravel/getting-started" /> + } title="Get started with Symfony" arrow="true" href="/docs/symfony/getting-started" /> + + + +## How it works + +Bref sets up API Gateway with AWS Lambda and the [PHP-FPM runtime](../runtimes/fpm-runtime.mdx). This is done via the `php-xx-fpm` runtime and the `httpApi` event: + +```yml filename="serverless.yml" +functions: + web: + handler: public/index.php + runtime: php-81-fpm + events: + - httpApi: '*' +``` + +This configuration deploys an API Gateway that forwards all routes (`*` is a wildcard) to AWS Lambda. + +On Lambda, the `php-81-fpm` runtime starts PHP-FPM and forwards all requests to it. PHP-FPM then runs the PHP code. + +This is perfect for most use-cases: **PHP works like on any server** with PHP-FPM. HTTP routing based on the URL is done by the application/the framework. + +The `handler` is the entrypoint of the application, usually `public/index.php` in most frameworks. That entrypoint kicks off the framework/your application, which does the routing and invokes the controllers, as usual. All the usual environment variables (like `$_GET`, `$_SERVER`, etc.) and functions (`header()`, etc.) work. + +That works well with frameworks like Symfony or Laravel that have a single entrypoint (e.g. `public/index.php`). + +Read the [PHP-FPM runtime documentation](../runtimes/fpm-runtime.mdx) to learn more. diff --git a/docs/use-cases/http/_meta.json b/docs/use-cases/http/_meta.json new file mode 100644 index 000000000..427d8d798 --- /dev/null +++ b/docs/use-cases/http/_meta.json @@ -0,0 +1,5 @@ +{ + "custom-domains": "Custom domains", + "binary-requests-responses": "Binary requests and responses", + "advanced-use-cases": "Advanced HTTP use cases" +} \ No newline at end of file diff --git a/docs/use-cases/http/advanced-use-cases.mdx b/docs/use-cases/http/advanced-use-cases.mdx new file mode 100644 index 000000000..e9085f1dc --- /dev/null +++ b/docs/use-cases/http/advanced-use-cases.mdx @@ -0,0 +1,308 @@ +import { Callout, Tab, Tabs } from 'nextra/components'; + +# Advanced HTTP use-cases + + + If you are getting started with Bref, read the guides for **Laravel** ([Get started](../../laravel/getting-started.mdx)), **Symfony** ([Get started](../../symfony/getting-started.mdx)), or other PHP frameworks ([Get started](../../default/getting-started.mdx)). + + This documentation is for advanced use-cases. + + +## Alternative AWS architectures + +By default, Bref uses API Gateway v2 HTTP APIs to run HTTP applications. + +However, there are other ways to run HTTP applications on AWS Lambda: using [API Gateway](https://aws.amazon.com/api-gateway/), [Lambda Function URLs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html), or [AWS Application Load Balancer (ALB)](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html): + +- API Gateway v1 "REST" + Lambda +- API Gateway v2 "HTTP" + Lambda +- Lambda Function URL +- AWS ALB (Application Load Balancer) + Lambda + +Bref supports all 4, but API Gateway v2 "HTTP" is the default because it is the simplest option that supports both HTTP APIs and websites with custom domains. + + + If you are getting started, **stay with the defaults**. The documentation and integrations will be much simpler to follow. + + You can change the architecture later if you need to. + + +Here is a summary of the differences between the 4 options: + +| | API Gateway v1 REST | API Gateway v2 HTTP | Function URL | ALB | +|---------------------------------|:---------------------:|:-----------------------------:|:--------------------------------:|:----------------:| +| Pricing (Lambda cost excluded) | $3.5/million requests | $1/million requests | Free (no extra costs) | Starts at $22/mo | +| Custom domain | ✅ | ✅ | No, but possible with CloudFront | ✅ | +| Authorizers | IAM, Lambda, Cognito | IAM, Lambda, Cognito, OAuth 2 | IAM | OIDC, Cognito | +| CORS | ✅ | ✅ | ✅ | ❌ | +| CloudWatch metrics | ✅ | ✅ | ✅ | ✅ | +| CloudWatch access logs | ✅ | ✅ | ❌ | ✅ | +| Caching | ✅ | ❌ | ❌ | ❌ | +| API key management | ✅ | ❌ | ❌ | ❌ | +| Request transformation | ✅ | ❌ | ❌ | ❌ | +| Request/response validation | ✅ | ❌ | ❌ | ❌ | +| Maximum HTTP response timeout | 29s | 30s | 15 minutes | 15 minutes | +| Added latency to HTTP responses | 25ms | 15ms | 10ms | | + +You can read more details [in the "Choosing between REST APIs and HTTP APIs" AWS documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html). + +### API Gateway v2 HTTP API + +The simplest way to set up API Gateway is to have all incoming requests sent to our application in one Lambda function: + +```yml filename="serverless.yml" +functions: + hello: + handler: index.php + # ... + events: + - httpApi: '*' +``` + +That works well with frameworks like Symfony or Laravel that have a single entrypoint (e.g. `public/index.php`) combined with the [PHP-FPM runtime](../../runtimes/fpm-runtime.mdx). + +You can look at more advanced API Gateway routing options in the [serverless documentation](https://www.serverless.com/framework/docs/providers/aws/events/http-api). + +### Lambda Function URLs + +Since 2022, AWS Lambda can respond to HTTP requests via [Lambda Function URLs](https://aws.amazon.com/blogs/aws/announcing-aws-lambda-function-urls-built-in-https-endpoints-for-single-function-microservices/). This is a new way to invoke Lambda functions via HTTP without using API Gateway. + +You can deploy a Lambda Function URL [via the following configuration](https://www.serverless.com/framework/docs/providers/aws/guide/functions#lambda-function-urls): + +```yml filename="serverless.yml" +functions: + hello: + handler: index.php + # ... + url: true +``` + +### API Gateway v1 REST API + +The syntax is slightly different from API Gateway v2 HTTP APIs as we must use a different `events` configuration. Here is an example that sends all requests to a single Lambda function: + +```yml filename="serverless.yml" +functions: + hello: + handler: index.php + # ... + events: + - http: 'ANY /' + - http: 'ANY /{proxy+}' +``` + +Learn more [in the Serverless Framework documentation](https://www.serverless.com/framework/docs/providers/aws/events/apigateway). + +### Application Load Balancer + +Application Load Balancer (ALB) is a managed load balancer that can be used to route HTTP requests to Lambda functions. It is a more advanced option that is interesting at high scale as ALB can be much cheaper than API Gateway. + +```yml filename="serverless.yml" +functions: + hello: + handler: index.php + # ... + events: + - alb: + listenerArn: arn:aws:elasticloadbalancing:us-east-1:12345:listener/app/my-load-balancer/50dc6c495c0c9188/ + priority: 1 + conditions: + path: '/*' +``` + +Learn more [in the Serverless Framework documentation](https://www.serverless.com/framework/docs/providers/aws/events/alb). + +## PHP handlers + + + This section applies to all 4 approaches: API Gateway v1, v2, ALB, and Function URLs. + + Bref abstracts the differences so that the same code can be used with all 4 solutions. + + +There are two ways to handle HTTP events with PHP: + +- via the [PHP-FPM runtime](../../runtimes/fpm-runtime.mdx) (simplest, this is Bref's default) +- via the [Event-Driven Function runtime](../../runtimes/function.mdx) (more advanced) + +Here is a full comparison between both approaches: + +| | PHP-FPM runtime | Event-Driven Function handler | +|----------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------| +| What are the use cases? | To build websites, APIs, etc. This should be the **default approach** as it's compatible with mature PHP frameworks and tools. | Build event-driven microservices, or run Laravel Octane, or Symfony with a keep-alive process (like Roadrunner/Swoole). | +| Why does that solution exist? | For out-of-the-box compatibility with existing applications and frameworks. | To match how other languages run in AWS Lambda, i.e. to build very specialized HTTP endpoints. | +| How it runs under the hood | Using PHP-FPM. | Using the PHP CLI. | +| What does the routing (i.e. separate pages)? | Your PHP framework (one Lambda receives all the URLs). | API Gateway: we define one Lambda and one handler class per route. | +| How to read the request? | `$_GET`, `$_POST`, etc. | The `$request` parameter (PSR-7 request). | +| How to write a response? | `echo`, `header()` function, etc. | Returning a PSR-7 response from the handler class. | +| How does it work? | Bref turns an API Gateway event into a FastCGI (PHP-FPM) request. | Bref turns an API Gateway event into a PSR-7 request. | +| Is each request handled in a separate PHP process? | Yes (that's how PHP-FPM works). | Yes by default (Bref replicates that to avoid surprises) but can be disabled for optimal performances. | + +### With the PHP-FPM runtime + +This is perfect for most use-cases: **PHP works like on any server** with PHP-FPM. HTTP routing based on the URL is done by the application/the framework. + +This approach is already covered by most of the Bref documentation, so we won't go into details here. + +You can read more about [the PHP-FPM runtime here](../../runtimes/fpm-runtime.mdx). + +### With the Event-Driven Function runtime + +This is more advanced, as PHP does not run in a traditional PHP-FPM environment. It can be used with or without a PHP framework. + +When used with a framework, understand that the whole HTTP stack (like HTTP middlewares) of the framework does not run. You are responsible for invoking the PHP code that should run. + + + Note: this approach is used to run Laravel Octane or Symfony with a keep-alive process (like Roadrunner/Swoole). These use cases are not detailed here, read [about Laravel Octane](../../laravel/octane.mdx) or [about Symfony "Keep-Alive"](../../symfony/keep-alive.mdx) instead. + + +The `handler` must be a PHP function, or a PSR-15 implementation. Indeed, Bref natively supports the [PSR-15](https://www.php-fig.org/psr/psr-15/#2-interfaces) and [PSR-7](https://www.php-fig.org/psr/psr-7/) standards. Here is an example: + +```php +getQueryParams()['name'] ?? 'world'; + + return new Response(200, [], "Hello $name"); + } +} +``` + +Then, create a Lambda function that listens to HTTP events with the handler you created: + + + + ```yml filename="serverless.yml" + functions: + # ... + handler: App\MyHttpHandler + runtime: php-81 + # Lambda Function URL + url: true + # Or API Gateway + events: + # API Gateway v2 + - httpApi: 'GET /hello' + # API Gateway v1 + - http: 'GET hello' + ``` + + The `App\MyHttpHandler` class will be instantiated by Laravel's service container. + + + ```yml filename="serverless.yml" + functions: + # ... + handler: App\MyHttpHandler + runtime: php-81 + # Lambda Function URL + url: true + # Or API Gateway + events: + # API Gateway v2 + - httpApi: 'GET /hello' + # API Gateway v1 + - http: 'GET hello' + ``` + + The `App\MyHttpHandler` class will be instantiated by Symfony's service container. + + + ```yml filename="serverless.yml" + functions: + # ... + handler: handler.php + runtime: php-81 + # Lambda Function URL + url: true + # Or API Gateway + events: + # API Gateway v2 + - httpApi: 'GET /hello' + # API Gateway v1 + - http: 'GET hello' + ``` + + The file `handler.php` should return the handler instance: + + ```php filename="handler.php" + + + +Since a handler is a controller for a specific route, we can use the API Gateway routing to deploy multiple functions: + +```yml filename="serverless.yml" +functions: + create-article: + handler: App\CreateArticleController + runtime: php-81 + events: + - httpApi: 'POST /articles' + get-article: + handler: App\GetArticleController + runtime: php-81 + events: + - httpApi: 'GET /articles/{id}' +``` + +Path parameters (e.g. `{id}` in the example above) are available as request attributes in the PSR-7 request: + +```php +$id = $request->getAttribute('id'); +``` + +[Full reference of HTTP events in `serverless.yml`](https://www.serverless.com/framework/docs/providers/aws/events/http-api/). + +#### Lambda event and context + +The API Gateway event and Lambda context are available as attributes on the PSR-7 request: + +```php +/** @var $event Bref\Event\Http\HttpRequestEvent */ +$event = $request->getAttribute('lambda-event'); + +/** @var $context Bref\Context\Context */ +$context = $request->getAttribute('lambda-context'); +``` + +If you're looking for the request context array, for example when using a [Lambda authorizer](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html#http-api-lambda-authorizer.payload-format-response): + +```php +$requestContext = $request->getAttribute('lambda-event')->getRequestContext(); +``` + +## Cold starts + +AWS Lambda automatically destroys Lambda containers that have been unused for 10 minutes. Warming up a new container can take 250ms or more, especially if your application is large. This delay is called [cold start](https://mikhail.io/serverless/coldstarts/aws/). + +To mitigate cold starts for HTTP applications, you can periodically send an event to your Lambda including a `{warmer: true}` payload. This will trigger the Lambda function. Bref recognizes this event and immediately responds with a `Status: 100` without executing your code. + +You can set up such events using a schedule ([read this article for more details](https://www.jeremydaly.com/lambda-warmer-optimize-aws-lambda-function-cold-starts/)): + +```yml filename="serverless.yml" + events: + - httpApi: '*' + - schedule: + rate: rate(5 minutes) + input: + warmer: true +``` + +You can learn more how AWS Lambda scales and runs in the [Serverless Visually Explained](https://serverless-visually-explained.com/) course. diff --git a/docs/use-cases/http/binary-requests-responses.mdx b/docs/use-cases/http/binary-requests-responses.mdx new file mode 100644 index 000000000..d8e456a1c --- /dev/null +++ b/docs/use-cases/http/binary-requests-responses.mdx @@ -0,0 +1,27 @@ +import { Callout } from 'nextra/components'; + +# Binary requests and responses + +AWS Lambda is only used for executing code. Serving assets via PHP does not make sense as this would be a waste of resources and money. + + + Deploying a website with assets (e.g. CSS, JavaScript, images) is covered in [the "Websites" documentation](../websites.mdx). + + +In some cases however, you want to serve images (or other assets) via PHP. One example would be if you served generated images or PDFs via PHP. + +By default, API Gateway **does not support binary HTTP requests or responses** like images, PDF, binary files… To achieve this, you need to enable the option for binary media types in `serverless.yml` as well as define the `BREF_BINARY_RESPONSES` environment variable: + +```yml filename="serverless.yml" +provider: + # ... + apiGateway: + binaryMediaTypes: + - '*/*' + environment: + BREF_BINARY_RESPONSES: '1' +``` + +This will make API Gateway support binary file uploads and downloads, and Bref will automatically encode responses to base64 (which is what API Gateway expects for binary responses). + +Be aware that the max upload and download size is **6MB**. For larger files, use AWS S3. An example is available in [Serverless Visually Explained](https://serverless-visually-explained.com/). diff --git a/docs/environment/custom-domains-path-mapping.png b/docs/use-cases/http/custom-domains-path-mapping.png similarity index 100% rename from docs/environment/custom-domains-path-mapping.png rename to docs/use-cases/http/custom-domains-path-mapping.png diff --git a/docs/use-cases/http/custom-domains.mdx b/docs/use-cases/http/custom-domains.mdx new file mode 100644 index 000000000..e399e6960 --- /dev/null +++ b/docs/use-cases/http/custom-domains.mdx @@ -0,0 +1,48 @@ +import { Callout } from 'nextra/components'; +import { NextSeo } from 'next-seo'; + + + +# Custom domain names + +API Gateway generates random domain names for our applications: + +``` +https://.execute-api..amazonaws.com/ +``` + +It is possible to replace those URLs by a custom domain. + + + This guides assume you already own the domain name you will want to use. + + +The first thing to do is register the domain in **ACM** (AWS Certificate Manager) to get an HTTPS certificate. This step is not optional. + +- Open [this link](https://console.aws.amazon.com/acm/home?region=us-east-1#/wizard/) or manually go in the ACM Console and click "Request a new certificate" in the `us-east-1` region (the region used for global "edge" certificates) +- Add your domain name and click "Next" +- Choose the domain validation of your choice. + - domain validation will require you to add CNAME entries to your DNS configuration + - email validation will require you to click a link you will receive in an email sent to `admin@your-domain.com` + +After validating the domain and the certificate we can now link the custom domain to our application via API Gateway. + +- Open [API Gateway's "Custom Domain" configuration](https://console.aws.amazon.com/apigateway/main/publish/domain-names) +- **Switch to the region of your application** +- Click "Create" +- Enter your domain name, select the certificate you created above and save +- Edit the domain that was created +- Click "Configure API mappings" to add an "API mapping": select your application and the `$default` stage (or `dev` in some cases), for example: + + ![](./custom-domains-path-mapping.png) +- After saving the "API mappings", find the `API Gateway domain name` in the "Configurations" tab +- Create a CNAME entry in your DNS to point your domain name to this domain + +After waiting for the DNS change to propagate (sometimes up to several hours) your website is now accessible via your custom domain. + + + You can also take a look at the plugin [serverless-domain-manager](https://www.serverless.com/plugins/serverless-domain-manager). + It handles the custom domain creation and optionally adds the Route53 record if asked. It is still necessary to create the ACM certificate manually. + + A basic implementation is proposed here : https://www.serverless.com/blog/serverless-api-gateway-domain#create-a-custom-domain-in-api-gateway + diff --git a/docs/use-cases/kafka.mdx b/docs/use-cases/kafka.mdx new file mode 100644 index 000000000..ebb835226 --- /dev/null +++ b/docs/use-cases/kafka.mdx @@ -0,0 +1,21 @@ +# Kafka events + +To handle [Kafka events](https://docs.aws.amazon.com/lambda/latest/dg/with-kafka.html), extend the `KafkaHandler` class: + +```php +use Bref\Context\Context; +use Bref\Event\Kafka\KafkaEvent; +use Bref\Event\Kafka\KafkaEventHandler; + +class Handler extends KafkaEvent +{ + public function handleKafka(KafkaEvent $event, Context $context): void + { + foreach ($event->getRecords() as $record) { + $data = $record->getValue(); + + // do something + } + } +} +``` diff --git a/docs/use-cases/kinesis.mdx b/docs/use-cases/kinesis.mdx new file mode 100644 index 000000000..adc3be70f --- /dev/null +++ b/docs/use-cases/kinesis.mdx @@ -0,0 +1,23 @@ +# Kinesis stream processing + +To handle [Kinesis events](https://docs.aws.amazon.com/lambda/latest/dg/with-kinesis.html), extend the `KinesisHandler` class: + +```php +use Bref\Context\Context; +use Bref\Event\Kinesis\KinesisEvent; +use Bref\Event\Kinesis\KinesisHandler; + +class Handler extends KinesisHandler +{ + public function handleKinesis(KinesisEvent $event, Context $context): void + { + foreach ($event->getRecords() as $record) { + $data = $record->getData(); + + // do something + } + } +} +``` + +Learn more about using WebSockets in `serverless.yml` [in the Serverless Framework documentation](https://www.serverless.com/framework/docs/providers/aws/events/streams/). diff --git a/docs/use-cases/s3.mdx b/docs/use-cases/s3.mdx new file mode 100644 index 000000000..c16d49c95 --- /dev/null +++ b/docs/use-cases/s3.mdx @@ -0,0 +1,89 @@ +import { Callout, Tab, Tabs } from 'nextra/components'; + +# S3 file processing + +[S3 can trigger Lambda functions](https://docs.aws.amazon.com/lambda/latest/dg/with-s3.html) whenever a file is added, modified, or deleted in an S3 bucket. + +```mermaid +graph LR; +START:::mermaidHidden -->|file| S3(S3):::mermaidAwsColor; +S3 --> Lambda(Lambda):::mermaidAwsColor; +``` + +This can be used to process files, for example to resize images, generate thumbnails, convert videos, after they are uploaded to S3. + +To handle S3 events, extend the `S3Handler` class: + +```php +use Bref\Context\Context; +use Bref\Event\S3\S3Event; +use Bref\Event\S3\S3Handler; + +class MyHandler extends S3Handler +{ + public function handleS3(S3Event $event, Context $context): void + { + $bucketName = $event->getRecords()[0]->getBucket()->getName(); + $fileName = $event->getRecords()[0]->getObject()->getKey(); + + // do something with the file + } +} +``` + +Then, create a Lambda function that listens to S3 events with the handler you created: + + + + ```yml filename="serverless.yml" + functions: + # ... + resizeImage: + handler: App\MyHandler + events: + - s3: photos + ``` + + The `App\MyHandler` class will be instantiated by Laravel's service container. + + + ```yml filename="serverless.yml" + functions: + # ... + resizeImage: + handler: App\MyHandler + events: + - s3: photos + ``` + + The `App\MyHandler` class will be instantiated by Symfony's service container. + + + ```yml filename="serverless.yml" + functions: + # ... + resizeImage: + handler: handler.php + events: + - s3: photos + ``` + + The file `handler.php` should return the handler instance: + + ```php filename="handler.php" + + + +The S3 bucket will automatically be created on deployment. You can listen to an existing S3 bucket via [the `existing: true` option](https://www.serverless.com/framework/docs/providers/aws/events/s3/#using-existing-buckets). + +Learn more about all the options available for S3 in `serverless.yml` [in the Serverless Framework documentation](https://www.serverless.com/framework/docs/providers/aws/events/s3/). + + + Watch out for recursive triggers: if your Lambda function writes files to the same S3 bucket, it will trigger itself again. You can avoid this by using a different bucket for the output files, or by using a prefix for the output files. + diff --git a/docs/use-cases/sns.mdx b/docs/use-cases/sns.mdx new file mode 100644 index 000000000..9906563ce --- /dev/null +++ b/docs/use-cases/sns.mdx @@ -0,0 +1,23 @@ +# SNS events + +To handle SNS events, extend the `SnsHandler` class: + +```php +use Bref\Context\Context; +use Bref\Event\Sns\SnsEvent; +use Bref\Event\Sns\SnsHandler; + +class MyHandler extends SnsHandler +{ + public function handleSns(SnsEvent $event, Context $context): void + { + foreach ($event->getRecords() as $record) { + $message = $record->getMessage(); + + // do something + } + } +} +``` + +Learn more about using WebSockets in `serverless.yml` [in the Serverless Framework documentation](https://www.serverless.com/framework/docs/providers/aws/events/sns/). diff --git a/docs/use-cases/sqs.mdx b/docs/use-cases/sqs.mdx new file mode 100644 index 000000000..35cce9a6f --- /dev/null +++ b/docs/use-cases/sqs.mdx @@ -0,0 +1,150 @@ +import { Callout, Tab, Tabs } from 'nextra/components'; + +# SQS asynchronous tasks + +SQS is a service (like RabbitMQ) that allows you to queue messages (aka "jobs"). It is a good fit for asynchronous tasks because it integrates natively with AWS Lambda. + +```mermaid +graph LR; +START:::mermaidHidden -->|job| SQS(SQS):::mermaidAwsColor; +SQS -->|job| Lambda(Lambda):::mermaidAwsColor; +``` + +Whenever a new message (job) is sent to SQS, Lambda is invoked with the message data. That means that there is no need to poll SQS, or run daemon/long-running processes to wait for messages. Lambda is invoked only when there are messages to process. + + + If you are using Laravel or Symfony, you should look at the Laravel Queues integration or Symfony Messenger integration instead of integrating with SQS events directly: + + - [Laravel Queues integration](/docs/laravel/queues) + - [Symfony Messenger integration](/docs/symfony/messenger) + + +## Handling SQS events + +To handle [SQS events](https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html), extend the `SqsHandler` class: + +```php +use Bref\Context\Context; +use Bref\Event\Sqs\SqsEvent; +use Bref\Event\Sqs\SqsHandler; + +class MyHandler extends SqsHandler +{ + public function handleSqs(SqsEvent $event, Context $context): void + { + foreach ($event->getRecords() as $record) { + // We can retrieve the message body of each record via `->getBody()` + $body = $record->getBody(); + + // do something + } + } +} +``` + +Then, create a Lambda function that listens to SQS events with the handler you created: + + + + ```yml filename="serverless.yml" + functions: + # ... + resizeImage: + handler: App\MyHandler + events: + - sqs: + arn: arn:aws:sqs:eu-west-1:111111111111:queue-name + # process one message at a time + batchSize: 1 + ``` + + The `App\MyHandler` class will be instantiated by Laravel's service container. + + + ```yml filename="serverless.yml" + functions: + # ... + resizeImage: + handler: App\MyHandler + events: + - sqs: + arn: arn:aws:sqs:eu-west-1:111111111111:queue-name + # process one message at a time + batchSize: 1 + ``` + + The `App\MyHandler` class will be instantiated by Symfony's service container. + + + ```yml filename="serverless.yml" + functions: + # ... + resizeImage: + handler: handler.php + events: + - sqs: + arn: arn:aws:sqs:eu-west-1:111111111111:queue-name + # process one message at a time + batchSize: 1 + ``` + + The file `handler.php` should return the handler instance: + + ```php filename="handler.php" + + + +## Creating SQS queues + +It is possible to deploy a preconfigured SQS queue in `serverless.yml` using the [`Queue` feature of the Lift plugin](https://github.com/getlift/lift/blob/master/docs/queue.md). For example: + +```yml filename="serverless.yml" +constructs: + my-queue: + type: queue + worker: + handler: handler.php +``` + +## Partial Batch Response + +While handling a batch of records, you can mark it as partially successful to reprocess only the failed records. + +In your function declaration in `serverless.yml`, set `functionResponseType` to `ReportBatchItemFailures` to let your function return a partial success result if one or more messages in the batch have failed. + +```yml filename="serverless.yml" +functions: + worker: + handler: handler.php + events: + - sqs: + arn: arn:aws:sqs:eu-west-1:111111111111:queue-name + batchSize: 10 + functionResponseType: ReportBatchItemFailures +``` + +In your PHP code, you can now use the `markAsFailed` method: + +```php + public function handleSqs(SqsEvent $event, Context $context): void + { + foreach ($event->getRecords() as $record) { + // do something + + // if something went wrong, mark the record as failed + $this->markAsFailed($record); + } + } +``` + +## Learn more + +Learn more about all the options available for SQS events in `serverless.yml` [in the Serverless Framework documentation](https://www.serverless.com/framework/docs/providers/aws/events/sqs/). + +You can also learn more about SQS, workers, scaling queues, and dealing with errors in [Serverless Visually Explained](https://serverless-visually-explained.com/). diff --git a/docs/use-cases/static-websites.mdx b/docs/use-cases/static-websites.mdx new file mode 100644 index 000000000..7093696fe --- /dev/null +++ b/docs/use-cases/static-websites.mdx @@ -0,0 +1,16 @@ +# Static websites + +To serve a static website, we do not need PHP nor AWS Lambda. We can host the website on any static hosting service (like Netlify, Vercel, etc.) or on AWS with CloudFront and S3: + +```mermaid +graph LR; +START:::mermaidHidden -->|HTTP request| CloudFront(CloudFront):::mermaidAwsColor; +CloudFront -->|HTTP request| S3(S3):::mermaidAwsColor; +``` + +To deploy such a website with Bref and `serverless.yml`, we can use [Lift](https://github.com/getlift/lift): + +- The [`static-website` construct](https://github.com/getlift/lift/blob/master/docs/static-website.md) for plain static HTML websites +- The [`single-page-app` construct](https://github.com/getlift/lift/blob/master/docs/single-page-app.md) for Single-Page Applications like React or VueJS + +Lift allows configuring custom domains, root domain to www redirects, and more. Check out the documentation linked above for more details. diff --git a/docs/use-cases/websites.mdx b/docs/use-cases/websites.mdx new file mode 100644 index 000000000..dbb8025f4 --- /dev/null +++ b/docs/use-cases/websites.mdx @@ -0,0 +1,217 @@ +import { Callout, Tab, Tabs } from 'nextra/components'; +import { NextSeo } from 'next-seo'; + + + +# Serverless PHP websites + +In this guide, you will learn how to set up assets for your serverless PHP website. + + + This guide assumes that you have already gotten started with Bref. If you haven't, [get started first](../setup.mdx). + + +## Architecture + +Websites usually contain 2 parts: + +- PHP code, running on AWS Lambda + API Gateway (read the [HTTP applications](./http.mdx) guide) +- static assets (CSS, JS…), [hosted on AWS S3](https://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteHosting.html) + +To combine both, we can use [AWS CloudFront](https://aws.amazon.com/cloudfront/). CloudFront acts both as a CDN and as reverse proxy to route requests to PHP or assets on S3. + +![](./websites/cloudfront.svg) + +This lets us host everything under the same domain and support both HTTP and HTTPS. + + + If you don't want to use Cloudfront, you can read the [older version of this documentation](https://github.com/brefphp/bref/blob/d1dd690d020cd03f134010db456bb61a6d0ffafb/docs/websites.md#architectures) which featured running PHP and the assets on two different domains. + + +## Setup + +While it is possible to set up CloudFront manually, the easiest approach is to use the [Server-side website construct of the Lift plugin](https://github.com/getlift/lift/blob/master/docs/server-side-website.md). + +First install the plugin: + +```bash +serverless plugin install -n serverless-lift +``` + + + + Then add this configuration to `serverless.yml`: + + ```yml filename="serverless.yml" {5,7-15} + # ... + + plugins: + - ./vendor/bref/bref + - serverless-lift + + constructs: + website: + type: server-side-website + assets: + '/js/*': public/js + '/css/*': public/css + '/favicon.ico': public/favicon.ico + '/robots.txt': public/robots.txt + # add here any file or directory that needs to be served from S3 + ``` + + Before deploying, compile your assets: + + ```bash + npm run prod + ``` + + + Then add this configuration to `serverless.yml`: + + ```yml filename="serverless.yml" {5,7-15} + # ... + + plugins: + - ./vendor/bref/bref + - serverless-lift + + constructs: + website: + type: server-side-website + assets: + '/bundles/*': public/bundles + '/build/*': public/build + '/favicon.ico': public/favicon.ico + '/robots.txt': public/robots.txt + # add here any file or directory that needs to be served from S3 + ``` + + + If you are not using Flex, update `serverless.yml` to exclude assets from the deployment ([see the recipe](https://github.com/symfony/recipes-contrib/blob/master/bref/symfony-bridge/0.1/serverless.yaml#L35)) + + + Because this construct sets the `X-Forwarded-Host` header by default, you should add it in your `trusted_headers` config, otherwise Symfony might generate wrong URLs. + + ```yml filename="config/packages/framework.yaml" /, 'x-forwarded-host'/ + trusted_headers: [ 'x-forwarded-for', 'x-forwarded-proto', 'x-forwarded-port', 'x-forwarded-host' ] + ``` + + Before deploying, compile your assets: + + ```bash + php bin/console assets:install --env prod + + # if using Webpack Encore, additionally run + yarn encore production + ``` + + + Then add this configuration to `serverless.yml`: + + ```yml filename="serverless.yml" {5,7-15} + # ... + + plugins: + - ./vendor/bref/bref + - serverless-lift + + constructs: + website: + type: server-side-website + assets: + '/js/*': public/js + '/css/*': public/css + '/favicon.ico': public/favicon.ico + '/robots.txt': public/robots.txt + # add here any file or directory that needs to be served from S3 + ``` + + If you need to compile your assets, make sure to run the command before deploying. + + + +Now deploy everything: + +```bash +serverless deploy +``` + +Lift will create all the required resources and take care of uploading your assets to S3 automatically. +You can access your website using the URL that Lift outputs at the end the deployment. + + + The first deployment takes 5 minutes because CloudFront is a distributed service. The next deployments that do not modify CloudFront's configuration will not suffer from this delay. + + +## Assets in templates + + + + + Assets referenced in Blade templates should be via the `asset()` helper: + + ```blade + + ``` + + If your templates reference some assets via direct path, you should edit them to use the `asset()` helper: + + ```diff + - + + + ``` + + + For the above configuration to work, assets must be referenced in Twig templates via the `asset()` helper as [recommended by Symfony](https://symfony.com/doc/current/templates.html#linking-to-css-javascript-and-image-assets): + + ```diff + - + + + ``` + + + If your `serverless.yml` configuration has different CloudFront routes for assets than the directory layout in your codebase, you may need to update your templates to use the correct paths. + + + +## Custom domain name + + + When using CloudFront, the custom domain must be set up on CloudFront, not API Gateway. If you have already set up your domain on API Gateway you will need to remove it before continuing. + + +The first thing to do is register the domain in **ACM** (AWS Certificate Manager) to get an HTTPS certificate. This step is not optional. + +- Open [this link](https://console.aws.amazon.com/acm/home?region=us-east-1#/wizard/) or manually go in the ACM Console and click "Request a new certificate" **in the `us-east-1` region** (CloudFront requires certificates from `us-east-1` because it is a global service) +- Add your domain name and click "Next". +- Choose the domain validation of your choice: + - domain validation will require you to create DNS entries (this is **recommended** because it renews the certificate automatically) + - email validation will require you to click a link you will receive in an email sent to `admin@your-domain.com` + +Copy the ARN of the ACM certificate. It should look like this: + +``` +arn:aws:acm:us-east-1:216536346254:certificate/322f12ee-1165-4bfa-a41f-08c932a2935d +``` + +Next, add your domain name and certificate in `serverless.yml`: + +```yml filename="serverless.yml" +# ... + +constructs: + website: + # ... + domain: mywebsite.com + certificate: +``` + +The last step will be to point your domain name DNS records to the CloudFront domain: + +- copy the domain outputted by Lift during `serverless deploy` (or run `serverless info` to retrieve it) +- create a CNAME to point your domain name to this URL + - if you use Route53 you can read [the official guide](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-to-cloudfront-distribution.html) + - if you use another registrar and you want to point your root domain (without `www.`) to CloudFront, you will need to use a registrar that supports this (for example [CloudFlare allows this with a technique called CNAME flattening](https://support.cloudflare.com/hc/en-us/articles/200169056-Understand-and-configure-CNAME-Flattening)) + +Lift supports more advanced use cases like multiple domains, root domain to `www` redirects, and more. Check out [the Lift documentation](https://github.com/getlift/lift/blob/master/docs/server-side-website.md). diff --git a/docs/websites/cloudfront.svg b/docs/use-cases/websites/cloudfront.svg similarity index 100% rename from docs/websites/cloudfront.svg rename to docs/use-cases/websites/cloudfront.svg diff --git a/docs/use-cases/websockets.mdx b/docs/use-cases/websockets.mdx new file mode 100644 index 000000000..0ecdbdcc5 --- /dev/null +++ b/docs/use-cases/websockets.mdx @@ -0,0 +1,45 @@ +import { Callout, Tab, Tabs } from 'nextra/components'; + +# WebSockets + +WebSockets are great for bringing real-time updates to a web application. They allow sending events from the backend application to the frontend (JavaScript) application. + +Implementing WebSockets implies maintaining a long-lived connection between the JavaScript client and the backend. As you can imagine, that is not possible with AWS Lambda. Indeed, Lambda only runs code on events: it is impossible to run code continuously. + +API Gateway [can solve that problem](https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-develop.html): API Gateway maintains the long-lived WebSocket connections and invokes Lambda when an event happens (connection, disconnection, message). + +```mermaid +graph LR; +API(API Gateway):::mermaidAwsColor; +B1(Browser):::mermaidBlack -->|WebSocket connection| API; +B2(Browser):::mermaidBlack -->|WebSocket connection| API; +B3(Browser):::mermaidBlack -->|WebSocket connection| API; +API --> Lambda(Lambda):::mermaidAwsColor; +``` + +To handle WebSocket events, extend the `WebsocketHandler` class: + +```php +use Bref\Context\Context; +use Bref\Event\ApiGateway\WebsocketEvent; +use Bref\Event\ApiGateway\WebsocketHandler; +use Bref\Event\Http\HttpResponse; + +class MyHandler extends WebsocketHandler +{ + public function handleWebsocket(WebsocketEvent $event, Context $context): HttpResponse + { + $route = $event->getRouteKey(); + $eventType = $event->getEventType(); + $body = $event->getBody(); + + return new HttpResponse('ok'); + } +} +``` + +Learn more about using WebSockets in `serverless.yml` [in the Serverless Framework documentation](https://www.serverless.com/framework/docs/providers/aws/events/websocket/). + + + A complete WebSocket example is available in [Serverless Visually Explained](https://serverless-visually-explained.com/). + diff --git a/docs/web-apps/cron.md b/docs/web-apps/cron.md deleted file mode 100644 index bdbaf2cbe..000000000 --- a/docs/web-apps/cron.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: CLI cron tasks on AWS Lambda -current_menu: web-cron -introduction: Learn how to create CLI serverless cron tasks with PHP on AWS Lambda. -previous: - link: /docs/runtimes/console.html - title: Console commands -next: - link: /docs/web-apps/local-development.html - title: Local development for web apps ---- - -AWS Lambda lets us run [console commands](/docs/runtimes/console.md) as cron tasks using the `schedule` event: - -```yaml -functions: - cron: - handler: bin/console # or 'artisan' for Laravel - runtime: php-81-console - events: - - schedule: - rate: rate(1 hour) - input: '"list --verbose"' -``` - -The example above will run the `bin/console list --verbose` command every hour in AWS Lambda. - -Note that the command **is a double quoted string**: `'"command"'`. -Why? The `input` value needs to be valid JSON, which means that it needs to be a JSON string, stored in a YAML string. - -Read more about the `schedule` feature in the [Serverless documentation](https://www.serverless.com/framework/docs/providers/aws/events/schedule/). - -> If you are interested in **cron functions** instead, read the [Cron functions](/docs/function/cron.html) documentation. diff --git a/docs/web-apps/docker.md b/docs/web-apps/docker.md deleted file mode 100644 index 4e544a0c8..000000000 --- a/docs/web-apps/docker.md +++ /dev/null @@ -1,107 +0,0 @@ ---- -title: Docker -current_menu: web-docker -previous: - link: local-development.html - title: Local development for web apps ---- - -AWS Lambda supports running a Docker image, instead of running your application in the default Linux environment. We recommend Docker **as a last resort**, as it is less practical and usually comes with slightly worse cold starts. Yes, Docker is great and probably sounds familiar, but is often not worth it on Lambda. - -You should consider deploying using Docker when: - -- Your Lambda Function is [larger than 250MB when unzipped](../environment/storage.md) -- You reached the limit of 5 Lambda layers (e.g. for extra PHP extensions) -- You need resources installed locally (e.g. mysqldump) - -> Note: this documentation page assumes that you have read about [web apps on Lambda](../runtimes/http.md) first. - -## Docker Image - -Bref helps you deploy to AWS Lambda using Docker by offering -out-of-the-box base images that are package for the Lambda environment. -Here is an example of a Docker image - -```Dockerfile -FROM bref/php-80-fpm:2 - -COPY . /var/task - -# Configure the handler file (the entrypoint that receives all HTTP requests) -CMD ["public/index.php"] -``` - -This Dockerfile outlines the 3 key aspects of Docker on Lambda: - -- Base image compatible with Lambda Runtime -- Source code placed under `/var/task` -- CMD pointing to the entrypoint that will handle requests - -You may also enable PHP extensions by pulling them from -[Bref Extensions](https://github.com/brefphp/extra-php-extensions) - -```Dockerfile -FROM bref/php-80-fpm:2 - -COPY --from=bref/extra-redis-php-80:1 /opt /opt -COPY --from=bref/extra-gmp-php-80:1 /opt /opt - -COPY . /var/task - -CMD ["public/index.php"] -``` - -## Deployment - -The Serverless Framework supports deploying Docker images to Lambda: - -```yaml -service: bref-with-docker - -provider: - name: aws - ecr: - images: - hello-world: - path: ./ - -functions: - hello: - image: - name: hello-world - events: - - httpApi: '*' -``` - -Instead of having a `handler` and a `runtime`, we'll declare an -`image`. In the `provider` block, we'll declare Docker images -that we want to build and deploy. - -When running `serverless deploy`, the framework will: - -- Build the Docker images according to their specified `path` -- Create an ECR Repository called `serverless-{service}-{env}` -- Authenticate against your ECR Account -- Push the newly built Docker Image -- Deploy the Lambda Function pointing to the Docker Image - -When the deployment finishes, your lambda is ready to be -invoked from your API Gateway address. - -## Filesystem - -The filesystem for Docker on AWS Lambda is also readonly with -a limited disk space under `/tmp` for read/write. This folder -will always be empty when a new cold start happens. Avoid -writing content to `/tmp` in your Dockerfile because that -content will **not be available** for your Lambda function. - -[Read more about file storage in Lambda](../environment/storage.md). - -## Docker Registry - -AWS Lambda only support AWS ECR as the source location for -Docker images. The Lambda service will use the image digest -as the unique identifier. This means that even if you overwrite -the exact same tag on ECR, your lambda will still run the previous -image code until you actually redeploy using the new image. diff --git a/docs/websites.md b/docs/websites.md deleted file mode 100644 index dec0c5c90..000000000 --- a/docs/websites.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -title: Creating serverless PHP websites -current_menu: websites -introduction: Learn how to deal with assets and static files to deploy serverless PHP websites. -previous: - link: /docs/runtimes/http.html - title: Web applications on AWS Lambda -next: - link: /docs/runtimes/console.html - title: Console commands ---- - -> Before reading this article we assume that you have read [Bref's introduction](/docs/first-steps.md) and that you are familiar with [Bref's HTTP runtime](/docs/runtimes/http.md). - -## Architectures - -Websites usually contain 2 parts: - -- PHP code, running on [AWS Lambda + API Gateway with the HTTP runtime](/docs/runtimes/http.md) -- static assets (CSS, JS…), [hosted on AWS S3](https://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteHosting.html) - -![](websites/cloudfront.svg) - -[CloudFront](https://aws.amazon.com/cloudfront/) (the AWS CDN) serves as an HTTP/HTTPS proxy. - -This lets us host everything under the same domain and support both HTTP and HTTPS. - -The easiest approach is to use the Server-side website construct of the Lift plugin. If you don't want to use it, see the [alternative solutions](#alternative-solutions). - -First install the plugin - -```bash -serverless plugin install -n serverless-lift -``` - -Then add this configuration to your `serverless.yml` file. - -```yaml -service: my-app - -provider: - ... - -plugins: - - ./vendor/bref/bref - - serverless-lift - -functions: - ... - -constructs: - website: - type: server-side-website - assets: - '/js/*': assets/js - '/css/*': assets/css - '/favicon.ico': assets/favicon.ico - '/robots.txt': assets/robots.txt - # add here any file or directory that needs to be served from S3 -``` - -Now deploy your website using `serverless deploy`. Lift will create all required resources and take care of -uploading your assets to S3 automatically. - -You can access your website using the URL that Lift outputs at the end the deployment. - -> The first deployment takes a lot of time (5 to 10 minutes) because CloudFront is a distributed service. The next deployments that do not modify CloudFront's configuration will not suffer from this delay. - -### Setting up a domain name - -Just like in the "[Custom domains](/docs/environment/custom-domains.md)" guide, you need to register your domain in **ACM** (AWS Certificate Manager) to get an HTTPS certificate. - -> If you have already set up this domain as a custom domain in API Gateway (by following the [Custom domain](/docs/environment/custom-domains.md) guide), you will need to remove it before continuing. - -- open [this link](https://console.aws.amazon.com/acm/home?region=us-east-1#/wizard/) or manually go in the ACM Console and click "Request a new certificate" **in the `us-east-1` region** (CloudFront requires certificates from `us-east-1`) -- add your domain name and click "Next" -- choose the domain validation of your choice - - domain validation will require you to create DNS entries (this is **recommended** because it renews the certificate automatically) - - email validation will require you to click a link you will receive in an email sent to `admin@your-domain.com` - -Copy the ARN of the ACM certificate. It should look like this: - -``` -arn:aws:acm:us-east-1:216536346254:certificate/322f12ee-1165-4bfa-a41f-08c932a2935d -``` - -Now add your domain name and certificate in your `serverless.yml` file - -```yaml -... -constructs: - website: - # ... - domain: mywebsite.com - certificate: -``` - -The last step will be to point your domain name DNS records to the CloudFront URL: - -- copy the domain outputted by Lift during `serverless deploy` (or run `serverless info` to retrieve it) -- create a CNAME to point your domain name to this URL - - if you use Route53 you can read [the official guide](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-to-cloudfront-distribution.html) - - if you use another registrar and you want to point your root domain (without `www.`) to CloudFront, you will need to use a registrar that supports this (for example [CloudFlare allows this with a technique called CNAME flattening](https://support.cloudflare.com/hc/en-us/articles/200169056-Understand-and-configure-CNAME-Flattening)) - -Lift supports more advanced use cases like multiple domains, root domain to `www` redirects, and more. Check out the official documentation. - -## Alternative solutions - -If you don't want to use Lift or Cloudfront, you may want to read the older version of this documentation which featured configuration using S3 only on a separate domain and Cloudfront configured through plain CloudFormation. diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 000000000..b8fe6273c --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,7 @@ +src/pages/docs +.next +.vercel +public/robots.txt +public/sitemap.xml +public/sitemap-*.xml +links.log diff --git a/website/Makefile b/website/Makefile new file mode 100644 index 000000000..5b88c306e --- /dev/null +++ b/website/Makefile @@ -0,0 +1,20 @@ +preview: node_modules + $(MAKE) preview-parallel-jobs -j2 +preview-parallel-jobs: live-sync-doc-files next-dev +live-sync-doc-files: + npx sync-directory ../docs src/pages/docs --deleteOrphaned --watch +next-dev: + npm run dev + +# Generate and deploy the production version of the website +website: node_modules src/pages/docs + npm run build + +src/pages/docs: + cp -r ../docs src/pages/docs + +node_modules: + npm install + +check-links: + node link-checker/index.js https://bref-website.vercel.app > links.log diff --git a/website/link-checker/ManualTests.md b/website/link-checker/ManualTests.md new file mode 100644 index 000000000..85bbd5365 --- /dev/null +++ b/website/link-checker/ManualTests.md @@ -0,0 +1,71 @@ +Test the following anchor links manually. + +### Tested OK + +/docs/#why-serverless +/docs/#why-bref +/docs/#maturity-matrix +/docs/runtimes/http#setup +/docs/runtimes/http#handler +/docs/runtimes/http#runtime +/docs/runtimes/http#routing +/docs/runtimes/http#binary-requests-and-responses +/docs/runtimes/http#context-access +/docs/runtimes/http#lambda-context +/docs/runtimes/http#request-context +/docs/runtimes/http#cold-starts +/docs/frameworks/laravel#setup +/docs/frameworks/laravel#deployment +/docs/frameworks/laravel#assets +/docs/frameworks/laravel#file-storage-on-s3 +/docs/frameworks/laravel#public-files +/docs/frameworks/laravel#laravel-queues +/docs/frameworks/laravel#how-it-works-1 +/docs/frameworks/laravel#laravel-octane +/docs/frameworks/laravel#persistent-database-connections +/docs/frameworks/laravel#caching +/docs/frameworks/laravel#maintenance-mode +/docs/frameworks/laravel#laravel-passport +/docs/web-apps/docker#docker-image +/docs/web-apps/docker#deployment +/docs/web-apps/docker#filesystem +/docs/web-apps/docker#docker-registry +/docs/runtimes/console#configuration +/docs/runtimes/console#usage +/docs/runtimes/console#lambda-context +/docs/runtimes/console#usage-without-serverless-framework +/docs/websites#setting-up-a-domain-name +/docs/web-apps/local-development#the-simple-way +/docs/web-apps/local-development#docker +/docs/web-apps/local-development#read-only-filesystem +/docs/web-apps/local-development#assets +/docs/web-apps/local-development#xdebug +/docs/web-apps/local-development#blackfire +/docs/web-apps/local-development#console-applications +/docs/frameworks/symfony#setup +/docs/frameworks/symfony#logs +/docs/frameworks/symfony#deploy +/docs/frameworks/symfony#console +/docs/frameworks/symfony#assets +/docs/frameworks/symfony#symfony-messenger +/docs/frameworks/symfony#caching +/docs/frameworks/symfony#trust-api-gateway +/docs/function/handlers#autoloading +/docs/function/handlers#s3-events +/docs/function/handlers#sqs-events +/docs/function/handlers#partial-batch-response +/docs/function/handlers#lift-queue-construct +/docs/function/handlers#api-gateway-http-events +/docs/function/handlers#lambda-event-and-context +/docs/function/handlers#websocket-events +/docs/function/handlers#eventbridge-events +/docs/function/handlers#sns-events +/docs/function/handlers#dynamodb-events +/docs/function/handlers#kinesis-events +/docs/function/handlers#kafka-events +/docs/function/local-development#with-serverless-framework +/docs/function/local-development#api-gateway-local-development +/docs/function/local-development#without-serverless-framework +/docs/runtimes/function#deployment-configuration +/docs/environment/custom-domains#custom-domains-for-http-lambdas +/docs/runtimes/#bref-ping diff --git a/website/link-checker/index.js b/website/link-checker/index.js new file mode 100644 index 000000000..a19c79a96 --- /dev/null +++ b/website/link-checker/index.js @@ -0,0 +1,135 @@ +import {Command, Option, runExit} from 'clipanion'; +import fetch from 'node-fetch'; +import { Parser } from "htmlparser2"; +import urls from './urls.js'; + +runExit(class HelloCommand extends Command { + url = Option.String(); + + async execute() { + /** @type {Record} */ + const links = {}; + const brokenLinks = new Set(); + /** @type {Record} */ + const pageCache = {}; + await scan(this.context.stdout, this.url, links, brokenLinks, pageCache); + + // Also scan the URLs we whitelisted + for (const chunk of chunkArray(urls)) { + const promises = chunk + .map(link => scan(this.context.stdout, this.url + link, links, brokenLinks, pageCache)); + await Promise.all(promises); + } + + this.context.stdout.write(`Found ${brokenLinks.size} broken links\n`); + for (const link of brokenLinks) { + this.context.stdout.write(` ${link}\n`); + } + } +}); + +/** + * @param {Writable} stdout + * @param {string} url + * @param {Record} links + * @param {Set} brokenLinks + * @param {Record} pageCache + * @returns {Promise} + */ +async function scan(stdout, url, links, brokenLinks, pageCache) { + // Reserve the link to avoid infinite loops + if (links[url] === true) { + return; + } + links[url] = true; + + // stdout.write(`Scanning ${url}\n`); + + // Cache the page to avoid fetching it twice when it is linked via anchor tags + const urlWithoutAnchor = url.split('#')[0]; + if (pageCache[urlWithoutAnchor] === undefined) { + const response = await fetch(url); + // Ignore redirects to other domains (e.g. https://bref.sh/slack) + const originalDomain = new URL(url).hostname; + const finalDomain = new URL(response.url).hostname; + if (finalDomain !== originalDomain) { + //console.log(`Ignoring ${url} redirecting to ${response.url}`); + pageCache[urlWithoutAnchor] = false; + return; + } + if (! response.ok) { + pageCache[urlWithoutAnchor] = false; + } else { + pageCache[urlWithoutAnchor] = await response.text(); + } + } + const pageBody = pageCache[urlWithoutAnchor]; + + if (pageBody === false) { + // stdout.write(`Error: ${url}\n`); + brokenLinks.add(url); + return; + } + + // Extract the anchor link from the url + const anchorLink = url.split('#')[1] ?? undefined; + let foundAnchorLink = false; + + const parser = new Parser({ + onopentag(name, attributes) { + // Search for the anchor link + if (anchorLink && attributes.id === anchorLink) { + foundAnchorLink = true; + } + // Register new links + let newLink = attributes.href ?? undefined; + if (name === 'a' && newLink) { + // Turn the relative link into an absolute one + // but avoid double slashes + newLink = new URL(newLink, url).toString(); + // Ignore external links on a different domain + const domain = new URL(url).hostname; + if (! newLink.startsWith(`http://${domain}`) && ! newLink.startsWith(`https://${domain}`)) { + return; + } + if (links[newLink] !== undefined) { + // Ignore already scanned links + return; + } + // stdout.write(`Found new link ${newLink} on ${url}\n`); + links[newLink] = false; + } + }, + }); + parser.write(pageBody); + parser.end(); + + if (anchorLink && ! foundAnchorLink) { + // stdout.write(`Error: anchor link not found: ${anchorLink}\n`); + brokenLinks.add(url); + } + + // Scan all the links we found that have not been scanned yet + const linksToScan = Object.entries(links) + .filter(([, scanned]) => ! scanned) + .map(([link]) => link); + for (const chunk of chunkArray(linksToScan)) { + const promises = chunk + .map(link => scan(stdout, link, links, brokenLinks, pageCache)); + await Promise.all(promises); + } +} + +/** + * @template T + * @param {T[]} array + * @param {number} size + * @returns {T[][]} + */ +function chunkArray(array, size = 4) { + const results = []; + while (array.length) { + results.push(array.splice(0, size)); + } + return results; +} diff --git a/website/link-checker/package.json b/website/link-checker/package.json new file mode 100644 index 000000000..d17ac6b79 --- /dev/null +++ b/website/link-checker/package.json @@ -0,0 +1,9 @@ +{ + "type": "module", + "main": "index.js", + "dependencies": { + "clipanion": "^4.0.0-rc.2", + "htmlparser2": "^9.0.0", + "node-fetch": "^3.3.2" + } +} diff --git a/website/link-checker/urls.js b/website/link-checker/urls.js new file mode 100644 index 000000000..27d2229b2 --- /dev/null +++ b/website/link-checker/urls.js @@ -0,0 +1,40 @@ +export default [ + // v2 website + '/docs/', + '/docs/news/', + '/docs/news/02-bref-2.0', + '/docs/news/01-bref-1.0', + '/docs/installation', + '/docs/first-steps', + '/docs/runtimes/', + '/docs/runtimes/http', + '/docs/websites', + '/docs/runtimes/console', + '/docs/web-apps/cron', + '/docs/web-apps/local-development', + '/docs/web-apps/docker', + '/docs/frameworks/laravel', + '/docs/frameworks/symfony', + '/docs/runtimes/function', + '/docs/function/handlers', + '/docs/function/local-development', + '/docs/function/cron', + '/docs/deploy', + '/docs/monitoring', + '/docs/environment/serverless-yml', + '/docs/environment/variables', + '/docs/environment/php', + '/docs/environment/storage', + '/docs/environment/logs', + '/docs/environment/database', + '/docs/environment/database-planetscale', + '/docs/environment/custom-domains', + '/docs/environment/performances', + '/docs/upgrading/v2', + '/docs/case-studies', + '/docs/community', + '/docs/installation/aws-keys', + '/docs/environment/database-public', + '/docs/web-apps/local-development.html', + '/docs/web-apps/local-development', +]; diff --git a/website/next-sitemap.config.js b/website/next-sitemap.config.js new file mode 100644 index 000000000..2efb39f74 --- /dev/null +++ b/website/next-sitemap.config.js @@ -0,0 +1,5 @@ +/** @type {import('next-sitemap').IConfig} */ +module.exports = { + siteUrl: process.env.SITE_URL || 'https://bref.sh', + generateRobotsTxt: true, +} diff --git a/website/next.config.js b/website/next.config.js new file mode 100644 index 000000000..f57debbbd --- /dev/null +++ b/website/next.config.js @@ -0,0 +1,33 @@ +const { withPlausibleProxy } = require('next-plausible') + +const withNextra = require('nextra')({ + theme: 'nextra-theme-docs', + themeConfig: './theme.config.jsx', + // Show the copy button on all code blocks + // https://nextra.site/docs/guide/syntax-highlighting#copy-button + defaultShowCopyCode: true, +}) + +module.exports = withNextra(withPlausibleProxy()({ + // Redirect old .html links + async redirects() { + const { redirects } = require('./redirects'); + const redirectList = Object.entries(redirects) + .map(([source, destination]) => ({ + source, + destination, + permanent: true, + })); + return [ + { + source: '/docs/:path*.html', + destination: '/docs/:path*', + permanent: true, + }, + ...redirectList, + ] + }, +})); + +// If you have other Next.js configurations, you can pass them as the parameter: +// module.exports = withNextra({ /* other next.js config */ }) diff --git a/website/package-lock.json b/website/package-lock.json index d44732fe1..b51f33cfe 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -1,2655 +1,7453 @@ { "name": "website", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { + "dependencies": { + "@aws-sdk/client-cloudwatch": "^3.393.0", + "@docsearch/css": "^3.5.2", + "@docsearch/react": "^3.5.2", + "@heroicons/react": "^2.0.18", + "@octokit/graphql": "^7.0.1", + "next": "^13.4.9", + "next-plausible": "^3.11.1", + "next-seo": "^6.1.0", + "next-sitemap": "^4.2.3", + "nextra": "^2.8.0", + "nextra-theme-docs": "^2.8.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sharp": "^0.32.6" + }, "devDependencies": { - "tailwindcss": "^2.0" + "@tailwindcss/forms": "^0.5.5", + "autoprefixer": "^10.4.14", + "postcss": "^8.4.25", + "sync-directory": "^6.0.4", + "tailwindcss": "^3.3.2" } }, - "node_modules/@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, + "node_modules/@algolia/autocomplete-core": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", + "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", + "@algolia/autocomplete-shared": "1.9.3" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", + "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", "dependencies": { - "@babel/highlight": "^7.16.7" + "@algolia/autocomplete-shared": "1.9.3" }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", + "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", + "dependencies": { + "@algolia/autocomplete-shared": "1.9.3" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", + "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/cache-browser-local-storage": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.20.0.tgz", + "integrity": "sha512-uujahcBt4DxduBTvYdwO3sBfHuJvJokiC3BP1+O70fglmE1ShkH8lpXqZBac1rrU3FnNYSUs4pL9lBdTKeRPOQ==", + "dependencies": { + "@algolia/cache-common": "4.20.0" + } + }, + "node_modules/@algolia/cache-common": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.20.0.tgz", + "integrity": "sha512-vCfxauaZutL3NImzB2G9LjLt36vKAckc6DhMp05An14kVo8F1Yofb6SIl6U3SaEz8pG2QOB9ptwM5c+zGevwIQ==" + }, + "node_modules/@algolia/cache-in-memory": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.20.0.tgz", + "integrity": "sha512-Wm9ak/IaacAZXS4mB3+qF/KCoVSBV6aLgIGFEtQtJwjv64g4ePMapORGmCyulCFwfePaRAtcaTbMcJF+voc/bg==", + "dependencies": { + "@algolia/cache-common": "4.20.0" + } + }, + "node_modules/@algolia/client-account": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.20.0.tgz", + "integrity": "sha512-GGToLQvrwo7am4zVkZTnKa72pheQeez/16sURDWm7Seyz+HUxKi3BM6fthVVPUEBhtJ0reyVtuK9ArmnaKl10Q==", + "dependencies": { + "@algolia/client-common": "4.20.0", + "@algolia/client-search": "4.20.0", + "@algolia/transporter": "4.20.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.20.0.tgz", + "integrity": "sha512-EIr+PdFMOallRdBTHHdKI3CstslgLORQG7844Mq84ib5oVFRVASuuPmG4bXBgiDbcsMLUeOC6zRVJhv1KWI0ug==", + "dependencies": { + "@algolia/client-common": "4.20.0", + "@algolia/client-search": "4.20.0", + "@algolia/requester-common": "4.20.0", + "@algolia/transporter": "4.20.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.20.0.tgz", + "integrity": "sha512-P3WgMdEss915p+knMMSd/fwiHRHKvDu4DYRrCRaBrsfFw7EQHon+EbRSm4QisS9NYdxbS04kcvNoavVGthyfqQ==", + "dependencies": { + "@algolia/requester-common": "4.20.0", + "@algolia/transporter": "4.20.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.20.0.tgz", + "integrity": "sha512-N9+zx0tWOQsLc3K4PVRDV8GUeOLAY0i445En79Pr3zWB+m67V+n/8w4Kw1C5LlbHDDJcyhMMIlqezh6BEk7xAQ==", + "dependencies": { + "@algolia/client-common": "4.20.0", + "@algolia/requester-common": "4.20.0", + "@algolia/transporter": "4.20.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.20.0.tgz", + "integrity": "sha512-zgwqnMvhWLdpzKTpd3sGmMlr4c+iS7eyyLGiaO51zDZWGMkpgoNVmltkzdBwxOVXz0RsFMznIxB9zuarUv4TZg==", + "dependencies": { + "@algolia/client-common": "4.20.0", + "@algolia/requester-common": "4.20.0", + "@algolia/transporter": "4.20.0" + } + }, + "node_modules/@algolia/logger-common": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.20.0.tgz", + "integrity": "sha512-xouigCMB5WJYEwvoWW5XDv7Z9f0A8VoXJc3VKwlHJw/je+3p2RcDXfksLI4G4lIVncFUYMZx30tP/rsdlvvzHQ==" + }, + "node_modules/@algolia/logger-console": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.20.0.tgz", + "integrity": "sha512-THlIGG1g/FS63z0StQqDhT6bprUczBI8wnLT3JWvfAQDZX5P6fCg7dG+pIrUBpDIHGszgkqYEqECaKKsdNKOUA==", + "dependencies": { + "@algolia/logger-common": "4.20.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.20.0.tgz", + "integrity": "sha512-HbzoSjcjuUmYOkcHECkVTwAelmvTlgs48N6Owt4FnTOQdwn0b8pdht9eMgishvk8+F8bal354nhx/xOoTfwiAw==", + "dependencies": { + "@algolia/requester-common": "4.20.0" + } + }, + "node_modules/@algolia/requester-common": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.20.0.tgz", + "integrity": "sha512-9h6ye6RY/BkfmeJp7Z8gyyeMrmmWsMOCRBXQDs4mZKKsyVlfIVICpcSibbeYcuUdurLhIlrOUkH3rQEgZzonng==" + }, + "node_modules/@algolia/requester-node-http": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.20.0.tgz", + "integrity": "sha512-ocJ66L60ABSSTRFnCHIEZpNHv6qTxsBwJEPfYaSBsLQodm0F9ptvalFkHMpvj5DfE22oZrcrLbOYM2bdPJRHng==", + "dependencies": { + "@algolia/requester-common": "4.20.0" + } + }, + "node_modules/@algolia/transporter": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.20.0.tgz", + "integrity": "sha512-Lsii1pGWOAISbzeyuf+r/GPhvHMPHSPrTDWNcIzOE1SG1inlJHICaVe2ikuoRjcpgxZNU54Jl+if15SUCsaTUg==", + "dependencies": { + "@algolia/cache-common": "4.20.0", + "@algolia/logger-common": "4.20.0", + "@algolia/requester-common": "4.20.0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", "dev": true, "engines": { - "node": ">=6.9.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", - "dev": true, + "node_modules/@aws-crypto/crc32": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", + "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==", "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" } }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, + "node_modules/@aws-crypto/crc32/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-crypto/ie11-detection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", + "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" + "tslib": "^1.11.1" } }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, + "node_modules/@aws-crypto/ie11-detection/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", + "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" + "@aws-crypto/ie11-detection": "^3.0.0", + "@aws-crypto/sha256-js": "^3.0.0", + "@aws-crypto/supports-web-crypto": "^3.0.0", + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", + "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", + "dependencies": { + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" } }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, + "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", + "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", "dependencies": { - "color-name": "1.1.3" + "tslib": "^1.11.1" } }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, - "node_modules/@babel/highlight/node_modules/has-flag": { + "node_modules/@aws-crypto/util": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", + "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" } }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, + "node_modules/@aws-crypto/util/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-sdk/client-cloudwatch": { + "version": "3.393.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch/-/client-cloudwatch-3.393.0.tgz", + "integrity": "sha512-OoiYZ3hPzgyVErh6vcd3FzHPkv+caQhZXf96Wo7Zb/OPE2ei5TZabzOp/n3wglwktalOKpv164gDNhNtfzt6og==", "dependencies": { - "has-flag": "^3.0.0" + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.391.0", + "@aws-sdk/credential-provider-node": "3.391.0", + "@aws-sdk/middleware-host-header": "3.391.0", + "@aws-sdk/middleware-logger": "3.391.0", + "@aws-sdk/middleware-recursion-detection": "3.391.0", + "@aws-sdk/middleware-signing": "3.391.0", + "@aws-sdk/middleware-user-agent": "3.391.0", + "@aws-sdk/types": "3.391.0", + "@aws-sdk/util-endpoints": "3.391.0", + "@aws-sdk/util-user-agent-browser": "3.391.0", + "@aws-sdk/util-user-agent-node": "3.391.0", + "@smithy/config-resolver": "^2.0.3", + "@smithy/fetch-http-handler": "^2.0.3", + "@smithy/hash-node": "^2.0.3", + "@smithy/invalid-dependency": "^2.0.3", + "@smithy/middleware-content-length": "^2.0.3", + "@smithy/middleware-endpoint": "^2.0.3", + "@smithy/middleware-retry": "^2.0.3", + "@smithy/middleware-serde": "^2.0.3", + "@smithy/middleware-stack": "^2.0.0", + "@smithy/node-config-provider": "^2.0.3", + "@smithy/node-http-handler": "^2.0.3", + "@smithy/protocol-http": "^2.0.3", + "@smithy/smithy-client": "^2.0.3", + "@smithy/types": "^2.2.0", + "@smithy/url-parser": "^2.0.3", + "@smithy/util-base64": "^2.0.0", + "@smithy/util-body-length-browser": "^2.0.0", + "@smithy/util-body-length-node": "^2.0.0", + "@smithy/util-defaults-mode-browser": "^2.0.3", + "@smithy/util-defaults-mode-node": "^2.0.3", + "@smithy/util-retry": "^2.0.0", + "@smithy/util-utf8": "^2.0.0", + "@smithy/util-waiter": "^2.0.3", + "fast-xml-parser": "4.2.5", + "tslib": "^2.5.0" }, "engines": { - "node": ">=4" + "node": ">=14.0.0" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, + "node_modules/@aws-sdk/client-sso": { + "version": "3.391.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.391.0.tgz", + "integrity": "sha512-aT+O1CbWIWYlCtWK6g3ZaMvFNImOgFGurOEPscuedqzG5UQc1bRtRrGYShLyzcZgfXP+s0cKYJqgGeRNoWiwqA==", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/middleware-host-header": "3.391.0", + "@aws-sdk/middleware-logger": "3.391.0", + "@aws-sdk/middleware-recursion-detection": "3.391.0", + "@aws-sdk/middleware-user-agent": "3.391.0", + "@aws-sdk/types": "3.391.0", + "@aws-sdk/util-endpoints": "3.391.0", + "@aws-sdk/util-user-agent-browser": "3.391.0", + "@aws-sdk/util-user-agent-node": "3.391.0", + "@smithy/config-resolver": "^2.0.3", + "@smithy/fetch-http-handler": "^2.0.3", + "@smithy/hash-node": "^2.0.3", + "@smithy/invalid-dependency": "^2.0.3", + "@smithy/middleware-content-length": "^2.0.3", + "@smithy/middleware-endpoint": "^2.0.3", + "@smithy/middleware-retry": "^2.0.3", + "@smithy/middleware-serde": "^2.0.3", + "@smithy/middleware-stack": "^2.0.0", + "@smithy/node-config-provider": "^2.0.3", + "@smithy/node-http-handler": "^2.0.3", + "@smithy/protocol-http": "^2.0.3", + "@smithy/smithy-client": "^2.0.3", + "@smithy/types": "^2.2.0", + "@smithy/url-parser": "^2.0.3", + "@smithy/util-base64": "^2.0.0", + "@smithy/util-body-length-browser": "^2.0.0", + "@smithy/util-body-length-node": "^2.0.0", + "@smithy/util-defaults-mode-browser": "^2.0.3", + "@smithy/util-defaults-mode-node": "^2.0.3", + "@smithy/util-retry": "^2.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.5.0" }, "engines": { - "node": ">= 8" + "node": ">=14.0.0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, + "node_modules/@aws-sdk/client-sts": { + "version": "3.391.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.391.0.tgz", + "integrity": "sha512-y+KmorcUx9o5O99sXVPbhGUpsLpfhzYRaYCqxArLsyzZTCO6XDXMi8vg/xtS+b703j9lWEl5GxAv2oBaEwEnhQ==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/credential-provider-node": "3.391.0", + "@aws-sdk/middleware-host-header": "3.391.0", + "@aws-sdk/middleware-logger": "3.391.0", + "@aws-sdk/middleware-recursion-detection": "3.391.0", + "@aws-sdk/middleware-sdk-sts": "3.391.0", + "@aws-sdk/middleware-signing": "3.391.0", + "@aws-sdk/middleware-user-agent": "3.391.0", + "@aws-sdk/types": "3.391.0", + "@aws-sdk/util-endpoints": "3.391.0", + "@aws-sdk/util-user-agent-browser": "3.391.0", + "@aws-sdk/util-user-agent-node": "3.391.0", + "@smithy/config-resolver": "^2.0.3", + "@smithy/fetch-http-handler": "^2.0.3", + "@smithy/hash-node": "^2.0.3", + "@smithy/invalid-dependency": "^2.0.3", + "@smithy/middleware-content-length": "^2.0.3", + "@smithy/middleware-endpoint": "^2.0.3", + "@smithy/middleware-retry": "^2.0.3", + "@smithy/middleware-serde": "^2.0.3", + "@smithy/middleware-stack": "^2.0.0", + "@smithy/node-config-provider": "^2.0.3", + "@smithy/node-http-handler": "^2.0.3", + "@smithy/protocol-http": "^2.0.3", + "@smithy/smithy-client": "^2.0.3", + "@smithy/types": "^2.2.0", + "@smithy/url-parser": "^2.0.3", + "@smithy/util-base64": "^2.0.0", + "@smithy/util-body-length-browser": "^2.0.0", + "@smithy/util-body-length-node": "^2.0.0", + "@smithy/util-defaults-mode-browser": "^2.0.3", + "@smithy/util-defaults-mode-node": "^2.0.3", + "@smithy/util-retry": "^2.0.0", + "@smithy/util-utf8": "^2.0.0", + "fast-xml-parser": "4.2.5", + "tslib": "^2.5.0" + }, "engines": { - "node": ">= 8" + "node": ">=14.0.0" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.391.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.391.0.tgz", + "integrity": "sha512-mAzICedcg4bfL0mM5O6QTd9mQ331NLse1DMr6XL21ZZiLB48ej19L7AGV2xq5QwVbqKU3IVv1myRyhvpDM9jMg==", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@aws-sdk/types": "3.391.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" }, "engines": { - "node": ">= 8" + "node": ">=14.0.0" } }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true - }, - "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.391.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.391.0.tgz", + "integrity": "sha512-DJZmbmRMqNSfSV7UF8eBVhADz16KAMCTxnFuvgioHHfYUTZQEhCxRHI8jJqYWxhLTriS7AuTBIWr+1AIbwsCTA==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.391.0", + "@aws-sdk/credential-provider-process": "3.391.0", + "@aws-sdk/credential-provider-sso": "3.391.0", + "@aws-sdk/credential-provider-web-identity": "3.391.0", + "@aws-sdk/types": "3.391.0", + "@smithy/credential-provider-imds": "^2.0.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.0", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" }, "engines": { - "node": ">=0.4.0" + "node": ">=14.0.0" } }, - "node_modules/acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "dev": true, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.391.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.391.0.tgz", + "integrity": "sha512-LXHQwsTw4WBwRzD9swu8254Hao5MoIaGXIzbhX4EQ84dtOkKYbwiY4pDpLfcHcw3B1lFKkVclMze8WAs4EdEww==", "dependencies": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" + "@aws-sdk/credential-provider-env": "3.391.0", + "@aws-sdk/credential-provider-ini": "3.391.0", + "@aws-sdk/credential-provider-process": "3.391.0", + "@aws-sdk/credential-provider-sso": "3.391.0", + "@aws-sdk/credential-provider-web-identity": "3.391.0", + "@aws-sdk/types": "3.391.0", + "@smithy/credential-provider-imds": "^2.0.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.0", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.391.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.391.0.tgz", + "integrity": "sha512-KMlzPlBI+hBmXDo+EoFZdLgCVRkRa9B9iEE6x0+hQQ6g9bW6HI7cDRVdceR1ZoPasSaNAZ9QOXMTIBxTpn0sPQ==", + "dependencies": { + "@aws-sdk/types": "3.391.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.0", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" + }, "engines": { - "node": ">=0.4.0" + "node": ">=14.0.0" } }, - "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==", - "dev": true, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.391.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.391.0.tgz", + "integrity": "sha512-FT/WoiRHiKys+FcRwvjui0yKuzNtJdn2uGuI1hYE0gpW1wVmW02ouufLckJTmcw09THUZ4w53OoCVU5OY00p8A==", "dependencies": { - "color-convert": "^2.0.1" + "@aws-sdk/client-sso": "3.391.0", + "@aws-sdk/token-providers": "3.391.0", + "@aws-sdk/types": "3.391.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.0", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=14.0.0" } }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.391.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.391.0.tgz", + "integrity": "sha512-n0vYg82B8bc4rxKltVbVqclev7hx+elyS9pEnZs3YbnbWJq0qqsznXmDfLqd1TcWpa09PGXcah0nsRDolVThsA==", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "@aws-sdk/types": "3.391.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" }, "engines": { - "node": ">= 8" + "node": ">=14.0.0" } }, - "node_modules/arg": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.1.tgz", - "integrity": "sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==", - "dev": true - }, - "node_modules/autoprefixer": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.2.tgz", - "integrity": "sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ==", - "dev": true, - "peer": true, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.391.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.391.0.tgz", + "integrity": "sha512-+nyNr0rb2ixY7mU48nibr7L7gsw37y4oELhqgnNKhcjZDJ34imBwKIMFa64n21FdftmhcjR8IdSpzXE9xrkJ8g==", "dependencies": { - "browserslist": "^4.19.1", - "caniuse-lite": "^1.0.30001297", - "fraction.js": "^4.1.2", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" + "@aws-sdk/types": "3.391.0", + "@smithy/protocol-http": "^2.0.3", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" }, "engines": { - "node": "^10 || ^12 || >=14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.1.0" + "node": ">=14.0.0" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.391.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.391.0.tgz", + "integrity": "sha512-KOwl5zo16b17JDhqILHBStccBQ2w35em7+/6vdkJdUII6OU8aVIFTlIQT9wOUvd4do6biIRBMZG3IK0Rg7mRDQ==", + "dependencies": { + "@aws-sdk/types": "3.391.0", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" + }, "engines": { - "node": ">=8" + "node": ">=14.0.0" } }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.391.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.391.0.tgz", + "integrity": "sha512-hVR3z59G7pX4pjDQs9Ag1tMgbLeGXOzeAAaNP9fEtHSd3KBMAGQgN3K3b9WPjzE2W0EoloHRJMK4qxZErdde2g==", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@aws-sdk/types": "3.391.0", + "@smithy/protocol-http": "^2.0.3", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, + "node_modules/@aws-sdk/middleware-sdk-sts": { + "version": "3.391.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.391.0.tgz", + "integrity": "sha512-6ZXI3Z4QU+TnT5PwKWloGmRHG81tWeI18/zxf9wWzrO2NhYFvITzEJH0vWLLiXdWtn/BYfLULXtDvkTaepbI5A==", "dependencies": { - "fill-range": "^7.0.1" + "@aws-sdk/middleware-signing": "3.391.0", + "@aws-sdk/types": "3.391.0", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" }, "engines": { - "node": ">=8" + "node": ">=14.0.0" } }, - "node_modules/browserslist": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", - "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", - "dev": true, - "peer": true, + "node_modules/@aws-sdk/middleware-signing": { + "version": "3.391.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.391.0.tgz", + "integrity": "sha512-2pAJJlZqaHc0d+cz2FTVrQmWi8ygKfqfczHUo/loCtOaMNtWXBHb/JsLEecs6cXdizy6gi3YsLz6VZYwY4Ssxw==", "dependencies": { - "caniuse-lite": "^1.0.30001286", - "electron-to-chromium": "^1.4.17", - "escalade": "^3.1.1", - "node-releases": "^2.0.1", - "picocolors": "^1.0.0" + "@aws-sdk/types": "3.391.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/protocol-http": "^2.0.3", + "@smithy/signature-v4": "^2.0.0", + "@smithy/types": "^2.2.0", + "@smithy/util-middleware": "^2.0.0", + "tslib": "^2.5.0" }, - "bin": { - "browserslist": "cli.js" + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.391.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.391.0.tgz", + "integrity": "sha512-LdK9uMNA14zqRw3B79Mhy7GX36qld/GYo93xuu+lr+AQ98leZEdc6GUbrtNDI3fP1Z8TMQcyHUKBml4/B+wXpQ==", + "dependencies": { + "@aws-sdk/types": "3.391.0", + "@aws-sdk/util-endpoints": "3.391.0", + "@smithy/protocol-http": "^2.0.3", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.391.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.391.0.tgz", + "integrity": "sha512-kgfArsKLDJE71qQjfXiHiM5cZqgDHlMsqEx35+A65GmTWJaS1PGDqu3ZvVVU8E5mxnCCLw7vho21fsjvH6TBpg==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/middleware-host-header": "3.391.0", + "@aws-sdk/middleware-logger": "3.391.0", + "@aws-sdk/middleware-recursion-detection": "3.391.0", + "@aws-sdk/middleware-user-agent": "3.391.0", + "@aws-sdk/types": "3.391.0", + "@aws-sdk/util-endpoints": "3.391.0", + "@aws-sdk/util-user-agent-browser": "3.391.0", + "@aws-sdk/util-user-agent-node": "3.391.0", + "@smithy/config-resolver": "^2.0.3", + "@smithy/fetch-http-handler": "^2.0.3", + "@smithy/hash-node": "^2.0.3", + "@smithy/invalid-dependency": "^2.0.3", + "@smithy/middleware-content-length": "^2.0.3", + "@smithy/middleware-endpoint": "^2.0.3", + "@smithy/middleware-retry": "^2.0.3", + "@smithy/middleware-serde": "^2.0.3", + "@smithy/middleware-stack": "^2.0.0", + "@smithy/node-config-provider": "^2.0.3", + "@smithy/node-http-handler": "^2.0.3", + "@smithy/property-provider": "^2.0.0", + "@smithy/protocol-http": "^2.0.3", + "@smithy/shared-ini-file-loader": "^2.0.0", + "@smithy/smithy-client": "^2.0.3", + "@smithy/types": "^2.2.0", + "@smithy/url-parser": "^2.0.3", + "@smithy/util-base64": "^2.0.0", + "@smithy/util-body-length-browser": "^2.0.0", + "@smithy/util-body-length-node": "^2.0.0", + "@smithy/util-defaults-mode-browser": "^2.0.3", + "@smithy/util-defaults-mode-node": "^2.0.3", + "@smithy/util-retry": "^2.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.5.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" + "engines": { + "node": ">=14.0.0" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, + "node_modules/@aws-sdk/types": { + "version": "3.391.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.391.0.tgz", + "integrity": "sha512-QpYVFKMOnzHz/JMj/b8wb18qxiT92U/5r5MmtRz2R3LOH6ooTO96k4ozXCrYr0qNed1PAnOj73rPrrH2wnCJKQ==", + "dependencies": { + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" + }, "engines": { - "node": ">= 0.8" + "node": ">=14.0.0" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.391.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.391.0.tgz", + "integrity": "sha512-zv4sYDTQhNxyLoekcE02/nk3xvoo6yCHDy1kDJk0MFxOKaqUB+CvZdQBR4YBLSDlD4o4DUBmdYgKT58FfbM8sQ==", + "dependencies": { + "@aws-sdk/types": "3.391.0", + "tslib": "^2.5.0" + }, "engines": { - "node": ">=6" + "node": ">=14.0.0" } }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.310.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.310.0.tgz", + "integrity": "sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==", + "dependencies": { + "tslib": "^2.5.0" + }, "engines": { - "node": ">= 6" + "node": ">=14.0.0" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001307", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001307.tgz", - "integrity": "sha512-+MXEMczJ4FuxJAUp0jvAl6Df0NI/OfW1RWEE61eSmzS7hw6lz4IKutbhbXendwq8BljfFuHtu26VWsg4afQ7Ng==", - "dev": true, - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.391.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.391.0.tgz", + "integrity": "sha512-6ipHOB1WdCBNeAMJauN7l2qNE0WLVaTNhkD290/ElXm1FHGTL8yw6lIDIjhIFO1bmbZxDiKApwDiG7ROhaJoxQ==", + "dependencies": { + "@aws-sdk/types": "3.391.0", + "@smithy/types": "^2.2.0", + "bowser": "^2.11.0", + "tslib": "^2.5.0" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.391.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.391.0.tgz", + "integrity": "sha512-PVvAK/Lf4BdB1eJIZtyFpGSslGQwKpYt9/hKs5NlR+qxBMXU9T0DnTqH4GiXZaazvXr7OUVWitIF2b7iKBMTow==", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@aws-sdk/types": "3.391.0", + "@smithy/node-config-provider": "^2.0.3", + "@smithy/types": "^2.2.0", + "tslib": "^2.5.0" }, "engines": { - "node": ">=10" + "node": ">=14.0.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "node_modules/@aws-sdk/util-utf8-browser": { + "version": "3.259.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", + "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "tslib": "^2.3.1" } }, - "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==", - "dev": true, + "node_modules/@babel/runtime": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", + "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", "dependencies": { - "is-glob": "^4.0.1" + "regenerator-runtime": "^0.13.11" }, "engines": { - "node": ">= 6" + "node": ">=6.9.0" } }, - "node_modules/color": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.0.tgz", - "integrity": "sha512-hHTcrbvEnGjC7WBMk6ibQWFVDgEFTVmjrz2Q5HlU6ltwxv0JJN2Z8I7uRbWeQLF04dikxs8zgyZkazRJvSMtyQ==", - "dev": true, + "node_modules/@braintree/sanitize-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz", + "integrity": "sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==" + }, + "node_modules/@corex/deepmerge": { + "version": "4.0.43", + "resolved": "https://registry.npmjs.org/@corex/deepmerge/-/deepmerge-4.0.43.tgz", + "integrity": "sha512-N8uEMrMPL0cu/bdboEWpQYb/0i2K5Qn8eCsxzOmxSggJbbQte7ljMRoXm917AbntqTGOzdTu+vP3KOOzoC70HQ==" + }, + "node_modules/@docsearch/css": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.5.2.tgz", + "integrity": "sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA==" + }, + "node_modules/@docsearch/react": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.5.2.tgz", + "integrity": "sha512-9Ahcrs5z2jq/DcAvYtvlqEBHImbm4YJI8M9y0x6Tqg598P40HTEkX7hsMcIuThI+hTFxRGZ9hll0Wygm2yEjng==", "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" + "@algolia/autocomplete-core": "1.9.3", + "@algolia/autocomplete-preset-algolia": "1.9.3", + "@docsearch/css": "3.5.2", + "algoliasearch": "^4.19.1" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/@headlessui/react": { + "version": "1.7.15", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.15.tgz", + "integrity": "sha512-OTO0XtoRQ6JPB1cKNFYBZv2Q0JMqMGNhYP1CjPvcJvjz8YGokz8oAj89HIYZGN0gZzn/4kk9iUpmMF4Q21Gsqw==", "dependencies": { - "color-name": "~1.1.4" + "client-only": "^0.0.1" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/color-string": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.0.tgz", - "integrity": "sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==", - "dev": true, - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" + "node": ">=10" + }, + "peerDependencies": { + "react": "^16 || ^17 || ^18", + "react-dom": "^16 || ^17 || ^18" } }, - "node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true, - "engines": { - "node": ">= 12" + "node_modules/@heroicons/react": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.0.18.tgz", + "integrity": "sha512-7TyMjRrZZMBPa+/5Y8lN0iyvUU/01PeMGX2+RE7cQWpEUIcb4QotzUObFkJDejj/HUH4qjP/eQ0gzzKs2f+6Yw==", + "peerDependencies": { + "react": ">= 16" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "node_modules/cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dev": true, "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" }, "engines": { - "node": ">=10" + "node": ">=6.0.0" } }, - "node_modules/css-color-names": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", - "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", "dev": true, "engines": { - "node": "*" + "node": ">=6.0.0" } }, - "node_modules/css-unit-converter": { + "node_modules/@jridgewell/set-array": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz", - "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==", - "dev": true - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", "dev": true, - "bin": { - "cssesc": "bin/cssesc" - }, "engines": { - "node": ">=4" + "node": ">=6.0.0" } }, - "node_modules/defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, - "node_modules/detective": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", - "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", "dev": true, "dependencies": { - "acorn-node": "^1.6.1", - "defined": "^1.0.0", - "minimist": "^1.1.1" - }, - "bin": { - "detective": "bin/detective.js" - }, - "engines": { - "node": ">=0.8.0" + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" } }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true + "node_modules/@mdx-js/mdx": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-2.3.0.tgz", + "integrity": "sha512-jLuwRlz8DQfQNiUCJR50Y09CGPq3fLtmtUQfVrj79E0JWu3dvsVcxVIcfhR5h0iXu+/z++zDrYeiJqifRynJkA==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/mdx": "^2.0.0", + "estree-util-build-jsx": "^2.0.0", + "estree-util-is-identifier-name": "^2.0.0", + "estree-util-to-js": "^1.1.0", + "estree-walker": "^3.0.0", + "hast-util-to-estree": "^2.0.0", + "markdown-extensions": "^1.0.0", + "periscopic": "^3.0.0", + "remark-mdx": "^2.0.0", + "remark-parse": "^10.0.0", + "remark-rehype": "^10.0.0", + "unified": "^10.0.0", + "unist-util-position-from-estree": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/electron-to-chromium": { - "version": "1.4.65", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.65.tgz", - "integrity": "sha512-0/d8Skk8sW3FxXP0Dd6MnBlrwx7Qo9cqQec3BlIAlvKnrmS3pHsIbaroEi+nd0kZkGpQ6apMEre7xndzjlEnLw==", - "dev": true, - "peer": true + "node_modules/@mdx-js/mdx/node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, + "node_modules/@mdx-js/react": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-2.3.0.tgz", + "integrity": "sha512-zQH//gdOmuu7nt2oJR29vFhDv88oGPmVw6BggmrHeMI+xgEkp1B2dX9/bMBSYtK0dyLX/aOmesKS09g222K1/g==", "dependencies": { - "is-arrayish": "^0.2.1" + "@types/mdx": "^2.0.0", + "@types/react": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "react": ">=16" } }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "peer": true, + "node_modules/@napi-rs/simple-git": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git/-/simple-git-0.1.8.tgz", + "integrity": "sha512-BvOMdkkofTz6lEE35itJ/laUokPhr/5ToMGlOH25YnhLD2yN1KpRAT4blW9tT8281/1aZjW3xyi73bs//IrDKA==", "engines": { - "node": ">=6" + "node": ">= 10" + }, + "optionalDependencies": { + "@napi-rs/simple-git-android-arm-eabi": "0.1.8", + "@napi-rs/simple-git-android-arm64": "0.1.8", + "@napi-rs/simple-git-darwin-arm64": "0.1.8", + "@napi-rs/simple-git-darwin-x64": "0.1.8", + "@napi-rs/simple-git-linux-arm-gnueabihf": "0.1.8", + "@napi-rs/simple-git-linux-arm64-gnu": "0.1.8", + "@napi-rs/simple-git-linux-arm64-musl": "0.1.8", + "@napi-rs/simple-git-linux-x64-gnu": "0.1.8", + "@napi-rs/simple-git-linux-x64-musl": "0.1.8", + "@napi-rs/simple-git-win32-arm64-msvc": "0.1.8", + "@napi-rs/simple-git-win32-x64-msvc": "0.1.8" + } + }, + "node_modules/@napi-rs/simple-git-android-arm-eabi": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-android-arm-eabi/-/simple-git-android-arm-eabi-0.1.8.tgz", + "integrity": "sha512-JJCejHBB1G6O8nxjQLT4quWCcvLpC3oRdJJ9G3MFYSCoYS8i1bWCWeU+K7Br+xT+D6s1t9q8kNJAwJv9Ygpi0g==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" } }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, + "node_modules/@napi-rs/simple-git-android-arm64": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-android-arm64/-/simple-git-android-arm64-0.1.8.tgz", + "integrity": "sha512-mraHzwWBw3tdRetNOS5KnFSjvdAbNBnjFLA8I4PwTCPJj3Q4txrigcPp2d59cJ0TC51xpnPXnZjYdNwwSI9g6g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=0.8.0" + "node": ">= 10" } }, - "node_modules/fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, + "node_modules/@napi-rs/simple-git-darwin-arm64": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-darwin-arm64/-/simple-git-darwin-arm64-0.1.8.tgz", + "integrity": "sha512-ufy/36eI/j4UskEuvqSH7uXtp3oXeLDmjQCfKJz3u5Vx98KmOMKrqAm2H81AB2WOtCo5mqS6PbBeUXR8BJX8lQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8.6.0" + "node": ">= 10" } }, - "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==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, + "node_modules/@napi-rs/simple-git-darwin-x64": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-darwin-x64/-/simple-git-darwin-x64-0.1.8.tgz", + "integrity": "sha512-Vb21U+v3tPJNl+8JtIHHT8HGe6WZ8o1Tq3f6p+Jx9Cz71zEbcIiB9FCEMY1knS/jwQEOuhhlI9Qk7d4HY+rprA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 6" + "node": ">= 10" } }, - "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" + "node_modules/@napi-rs/simple-git-linux-arm-gnueabihf": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-arm-gnueabihf/-/simple-git-linux-arm-gnueabihf-0.1.8.tgz", + "integrity": "sha512-6BPTJ7CzpSm2t54mRLVaUr3S7ORJfVJoCk2rQ8v8oDg0XAMKvmQQxOsAgqKBo9gYNHJnqrOx3AEuEgvB586BuQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" } }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, + "node_modules/@napi-rs/simple-git-linux-arm64-gnu": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-arm64-gnu/-/simple-git-linux-arm64-gnu-0.1.8.tgz", + "integrity": "sha512-qfESqUCAA/XoQpRXHptSQ8gIFnETCQt1zY9VOkplx6tgYk9PCeaX4B1Xuzrh3eZamSCMJFn+1YB9Ut8NwyGgAA==", + "cpu": [ + "arm64" + ], + "hasInstallScript": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">= 10" } }, - "node_modules/fraction.js": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.2.tgz", - "integrity": "sha512-o2RiJQ6DZaR/5+Si0qJUIy637QMRudSi9kU/FFzx9EZazrIdnBgpU+3sEWCxAVhH2RtxW2Oz+T4p2o8uOPVcgA==", - "dev": true, - "peer": true, + "node_modules/@napi-rs/simple-git-linux-arm64-musl": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-arm64-musl/-/simple-git-linux-arm64-musl-0.1.8.tgz", + "integrity": "sha512-G80BQPpaRmQpn8dJGHp4I2/YVhWDUNJwcCrJAtAdbKFDCMyCHJBln2ERL/+IEUlIAT05zK/c1Z5WEprvXEdXow==", + "cpu": [ + "arm64" + ], + "hasInstallScript": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://www.patreon.com/infusion" + "node": ">= 10" } }, - "node_modules/fs-extra": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", - "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, + "node_modules/@napi-rs/simple-git-linux-x64-gnu": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-x64-gnu/-/simple-git-linux-x64-gnu-0.1.8.tgz", + "integrity": "sha512-NI6o1sZYEf6vPtNWJAm9w8BxJt+LlSFW0liSjYe3lc3e4dhMfV240f0ALeqlwdIldRPaDFwZSJX5/QbS7nMzhw==", + "cpu": [ + "x64" + ], + "hasInstallScript": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" + "node": ">= 10" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, + "node_modules/@napi-rs/simple-git-linux-x64-musl": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-x64-musl/-/simple-git-linux-x64-musl-0.1.8.tgz", + "integrity": "sha512-wljGAEOW41er45VTiU8kXJmO480pQKzsgRCvPlJJSCaEVBbmo6XXbFIXnZy1a2J3Zyy2IOsRB4PVkUZaNuPkZQ==", + "cpu": [ + "x64" + ], "hasInstallScript": true, "optional": true, "os": [ - "darwin" + "linux" ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 10" } }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, + "node_modules/@napi-rs/simple-git-win32-arm64-msvc": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-win32-arm64-msvc/-/simple-git-win32-arm64-msvc-0.1.8.tgz", + "integrity": "sha512-QuV4QILyKPfbWHoQKrhXqjiCClx0SxbCTVogkR89BwivekqJMd9UlMxZdoCmwLWutRx4z9KmzQqokvYI5QeepA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">= 10" } }, - "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==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, + "node_modules/@napi-rs/simple-git-win32-x64-msvc": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-win32-x64-msvc/-/simple-git-win32-x64-msvc-0.1.8.tgz", + "integrity": "sha512-UzNS4JtjhZhZ5hRLq7BIUq+4JOwt1ThIKv11CsF1ag2l99f0123XvfEpjczKTaa94nHtjXYc2Mv9TjccBqYOew==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=10.13.0" + "node": ">= 10" } }, - "node_modules/graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", - "dev": true + "node_modules/@next/env": { + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.9.tgz", + "integrity": "sha512-vuDRK05BOKfmoBYLNi2cujG2jrYbEod/ubSSyqgmEx9n/W3eZaJQdRNhTfumO+qmq/QTzLurW487n/PM/fHOkw==" }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, + "node_modules/@next/swc-darwin-arm64": { + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.9.tgz", + "integrity": "sha512-TVzGHpZoVBk3iDsTOQA/R6MGmFp0+17SWXMEWd6zG30AfuELmSSMe2SdPqxwXU0gbpWkJL1KgfLzy5ReN0crqQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 0.4.0" + "node": ">= 10" } }, - "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==", - "dev": true, + "node_modules/@next/swc-darwin-x64": { + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.9.tgz", + "integrity": "sha512-aSfF1fhv28N2e7vrDZ6zOQ+IIthocfaxuMWGReB5GDriF0caTqtHttAvzOMgJgXQtQx6XhyaJMozLTSEXeNN+A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8" + "node": ">= 10" } }, - "node_modules/hex-color-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", - "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", - "dev": true - }, - "node_modules/hsl-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", - "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=", - "dev": true + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.9.tgz", + "integrity": "sha512-JhKoX5ECzYoTVyIy/7KykeO4Z2lVKq7HGQqvAH+Ip9UFn1MOJkOnkPRB7v4nmzqAoY+Je05Aj5wNABR1N18DMg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/hsla-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", - "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=", - "dev": true + "node_modules/@next/swc-linux-arm64-musl": { + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.9.tgz", + "integrity": "sha512-OOn6zZBIVkm/4j5gkPdGn4yqQt+gmXaLaSjRSO434WplV8vo2YaBNbSHaTM9wJpZTHVDYyjzuIYVEzy9/5RVZw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/html-tags": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", - "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==", - "dev": true, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.9.tgz", + "integrity": "sha512-iA+fJXFPpW0SwGmx/pivVU+2t4zQHNOOAr5T378PfxPHY6JtjV6/0s1vlAJUdIHeVpX98CLp9k5VuKgxiRHUpg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">= 10" } }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.9.tgz", + "integrity": "sha512-rlNf2WUtMM+GAQrZ9gMNdSapkVi3koSW3a+dmBVp42lfugWVvnyzca/xJlN48/7AGx8qu62WyO0ya1ikgOxh6A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 10" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.9.tgz", + "integrity": "sha512-5T9ybSugXP77nw03vlgKZxD99AFTHaX8eT1ayKYYnGO9nmYhJjRPxcjU5FyYI+TdkQgEpIcH7p/guPLPR0EbKA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.9.tgz", + "integrity": "sha512-ojZTCt1lP2ucgpoiFgrFj07uq4CZsq4crVXpLGgQfoFq00jPKRPgesuGPaz8lg1yLfvafkU3Jd1i8snKwYR3LA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "node_modules/@next/swc-win32-x64-msvc": { + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.9.tgz", + "integrity": "sha512-QbT03FXRNdpuL+e9pLnu+XajZdm/TtIXVYY4lA9t+9l0fLZbHXDYEKitAqxrOj37o3Vx5ufxiRAniaIebYDCgw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dependencies": { - "binary-extensions": "^2.0.0" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" }, "engines": { - "node": ">=8" + "node": ">= 8" } }, - "node_modules/is-color-stop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", - "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", - "dev": true, - "dependencies": { - "css-color-names": "^0.0.4", - "hex-color-regex": "^1.1.0", - "hsl-regex": "^1.0.0", - "hsla-regex": "^1.0.0", - "rgb-regex": "^1.0.1", - "rgba-regex": "^1.0.0" + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" } }, - "node_modules/is-core-module": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", - "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", - "dev": true, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dependencies": { - "has": "^1.0.3" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" }, - "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", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">= 8" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, + "node_modules/@octokit/endpoint": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.0.tgz", + "integrity": "sha512-szrQhiqJ88gghWY2Htt8MqUDO6++E/EIXqJ2ZEp5ma3uGS46o7LZAzSLt49myB7rT+Hfw5Y6gO3LmOxGzHijAQ==", "dependencies": { - "is-extglob": "^2.1.1" + "@octokit/types": "^11.0.0", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 18" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, + "node_modules/@octokit/graphql": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.1.tgz", + "integrity": "sha512-T5S3oZ1JOE58gom6MIcrgwZXzTaxRnxBso58xhozxHpOqSTgDS6YNeEUvZ/kRvXgPrRz/KHnZhtb7jUMRi9E6w==", + "dependencies": { + "@octokit/request": "^8.0.1", + "@octokit/types": "^11.0.0", + "universal-user-agent": "^6.0.0" + }, "engines": { - "node": ">=0.12.0" + "node": ">= 18" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "node_modules/@octokit/openapi-types": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.0.0.tgz", + "integrity": "sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw==" }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, + "node_modules/@octokit/request": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.1.tgz", + "integrity": "sha512-8N+tdUz4aCqQmXl8FpHYfKG9GelDFd7XGVzyN8rc6WxVlYcfpHECnuRkgquzz+WzvHTK62co5di8gSXnzASZPQ==", "dependencies": { - "universalify": "^2.0.0" + "@octokit/endpoint": "^9.0.0", + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^11.1.0", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "engines": { + "node": ">= 18" } }, - "node_modules/lilconfig": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", - "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==", - "dev": true, + "node_modules/@octokit/request-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.0.tgz", + "integrity": "sha512-1ue0DH0Lif5iEqT52+Rf/hf0RmGO9NWFjrzmrkArpG9trFfDM/efx00BJHdLGuro4BR/gECxCU2Twf5OKrRFsQ==", + "dependencies": { + "@octokit/types": "^11.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, "engines": { - "node": ">=10" + "node": ">= 18" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "node_modules/@octokit/types": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-11.1.0.tgz", + "integrity": "sha512-Fz0+7GyLm/bHt8fwEqgvRBWwIV1S6wRRyq+V6exRKLVWaKGsuy6H9QFYeBVDV7rK6fO3XwHgQOPxv+cLj2zpXQ==", + "dependencies": { + "@octokit/openapi-types": "^18.0.0" + } }, - "node_modules/lodash.topath": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.topath/-/lodash.topath-4.5.2.tgz", - "integrity": "sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak=", - "dev": true + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, + "node_modules/@smithy/abort-controller": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.0.4.tgz", + "integrity": "sha512-3+3/xRQ0K/NFVtKSiTGsUa3muZnVaBmHrLNgxwoBLZO9rNhwZtjjjf7pFJ6aoucoul/c/w3xobRkgi8F9MWX8Q==", + "dependencies": { + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" + }, "engines": { - "node": ">= 8" + "node": ">=14.0.0" } }, - "node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, + "node_modules/@smithy/config-resolver": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.0.4.tgz", + "integrity": "sha512-JtKWIKoCFeOY5JGQeEl81AKdIpzeLLSjSMmO5yoKqc58Yn3cxmteylT6Elba3FgAHjK1OthARRXz5JXaKKRB7g==", "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "@smithy/types": "^2.2.1", + "@smithy/util-config-provider": "^2.0.0", + "@smithy/util-middleware": "^2.0.0", + "tslib": "^2.5.0" }, "engines": { - "node": ">=8.6" + "node": ">=14.0.0" } }, - "node_modules/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", - "dev": true, + "node_modules/@smithy/credential-provider-imds": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.0.4.tgz", + "integrity": "sha512-vW7xoDKZwjjf/2GCwVf/uvZce/QJOAYan9r8UsqlzOrnnpeS2ffhxeZjLK0/emZu8n6qU3amGgZ/BTo3oVtEyQ==", "dependencies": { - "brace-expansion": "^1.1.7" + "@smithy/node-config-provider": "^2.0.4", + "@smithy/property-provider": "^2.0.4", + "@smithy/types": "^2.2.1", + "@smithy/url-parser": "^2.0.4", + "tslib": "^2.5.0" }, "engines": { - "node": "*" + "node": ">=14.0.0" } }, - "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "node_modules/modern-normalize": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/modern-normalize/-/modern-normalize-1.1.0.tgz", - "integrity": "sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node_modules/@smithy/eventstream-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-2.0.4.tgz", + "integrity": "sha512-DkVLcQjhOxPj/4pf2hNj2kvOeoLczirHe57g7czMNJCUBvg9cpU9hNgqS37Y5sjdEtMSa2oTyCS5oeHZtKgoIw==", + "dependencies": { + "@aws-crypto/crc32": "3.0.0", + "@smithy/types": "^2.2.1", + "@smithy/util-hex-encoding": "^2.0.0", + "tslib": "^2.5.0" } }, - "node_modules/nanoid": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", - "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" + "node_modules/@smithy/fetch-http-handler": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.0.4.tgz", + "integrity": "sha512-1dwR8T+QMe5Gs60NpZgF7ReZp0SXz1O/aX5BdDhsOJh72fi3Bx2UZlDihCdb++9vPyBRMXFRF7I8/C4x8iIm8A==", + "dependencies": { + "@smithy/protocol-http": "^2.0.4", + "@smithy/querystring-builder": "^2.0.4", + "@smithy/types": "^2.2.1", + "@smithy/util-base64": "^2.0.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.0.4.tgz", + "integrity": "sha512-vZ6a/fvEAFJKNtxJsn0I2WM8uBdypLLhLTpP4BA6fRsBAtwIl5S4wTt0Hspy6uGNn/74LmCxGmFSTMMbSd7ZDA==", + "dependencies": { + "@smithy/types": "^2.2.1", + "@smithy/util-buffer-from": "^2.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.5.0" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": ">=14.0.0" } }, - "node_modules/node-emoji": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", - "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", - "dev": true, + "node_modules/@smithy/invalid-dependency": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.0.4.tgz", + "integrity": "sha512-zfbPPZFiZvhIXJYKlzQwDUnxmWK/SmyDcM6iQJRZHU2jQZAzhHUXFGIu2lKH9L02VUqysOgQi3S/HY4fhrVT8w==", "dependencies": { - "lodash": "^4.17.21" + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" } }, - "node_modules/node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", - "dev": true, - "peer": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, + "node_modules/@smithy/is-array-buffer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz", + "integrity": "sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==", + "dependencies": { + "tslib": "^2.5.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=14.0.0" } }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", - "dev": true, - "peer": true, + "node_modules/@smithy/middleware-content-length": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.0.4.tgz", + "integrity": "sha512-Pdd+fhRbvizqsgYJ0pLWE6hjhq42wDFWzMj/1T7mEY9tG9bP6/AcdsQK8SAOckrBLURDoeSqTAwPKalsgcZBxw==", + "dependencies": { + "@smithy/protocol-http": "^2.0.4", + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=14.0.0" } }, - "node_modules/object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", - "dev": true, + "node_modules/@smithy/middleware-endpoint": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-2.0.4.tgz", + "integrity": "sha512-aLPqkqKjZQ1V718P0Ostpp53nWfwK32uD0HFKSAOT25RvL285dqzGl0PAKDXpyLsPsPmHe0Yrg0AUFkRv4CRbQ==", + "dependencies": { + "@smithy/middleware-serde": "^2.0.4", + "@smithy/types": "^2.2.1", + "@smithy/url-parser": "^2.0.4", + "@smithy/util-middleware": "^2.0.0", + "tslib": "^2.5.0" + }, "engines": { - "node": ">= 6" + "node": ">=14.0.0" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, + "node_modules/@smithy/middleware-retry": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-2.0.4.tgz", + "integrity": "sha512-stozO6NgH9W/OSfFMOJEtlJCsnJFSoGyV4LHzIVQeXTzZ2RHjmytQ/Ez7GngHGZ1YsB4zxE1qDTXAU0AlaKf2w==", "dependencies": { - "wrappy": "1" + "@smithy/protocol-http": "^2.0.4", + "@smithy/service-error-classification": "^2.0.0", + "@smithy/types": "^2.2.1", + "@smithy/util-middleware": "^2.0.0", + "@smithy/util-retry": "^2.0.0", + "tslib": "^2.5.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, + "node_modules/@smithy/middleware-retry/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.0.4.tgz", + "integrity": "sha512-oDttJMMES7yXmopjQHnqTkxu8vZOdjB9VpSj94Ff4/GXdKQH7ozKLNIPq4C568nbeQbBt/gsLb6Ttbx1+j+JPQ==", "dependencies": { - "callsites": "^3.0.0" + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" }, "engines": { - "node": ">=6" + "node": ">=14.0.0" } }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, + "node_modules/@smithy/middleware-stack": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.0.0.tgz", + "integrity": "sha512-31XC1xNF65nlbc16yuh3wwTudmqs6qy4EseQUGF8A/p2m/5wdd/cnXJqpniy/XvXVwkHPz/GwV36HqzHtIKATQ==", "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" + "tslib": "^2.5.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=14.0.0" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, + "node_modules/@smithy/node-config-provider": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.0.4.tgz", + "integrity": "sha512-s9O90cEhkpzZulvdHBBaroZ6AJ5uV6qtmycgYKP1yOCSfPHGIWYwaULdbfxraUsvzCcnMosDNkfckqXYoKI6jw==", + "dependencies": { + "@smithy/property-provider": "^2.0.4", + "@smithy/shared-ini-file-loader": "^2.0.4", + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=14.0.0" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, + "node_modules/@smithy/node-http-handler": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.0.4.tgz", + "integrity": "sha512-svqeqkGgQz1B2m3IurHtp1O8vfuUGbqw6vynFmOrvPirRdiIPukHTZW1GN/JuBCtDpq9mNPutSVipfz2n4sZbQ==", + "dependencies": { + "@smithy/abort-controller": "^2.0.4", + "@smithy/protocol-http": "^2.0.4", + "@smithy/querystring-builder": "^2.0.4", + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" + }, "engines": { - "node": ">=8" + "node": ">=14.0.0" } }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" + "node_modules/@smithy/property-provider": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.0.4.tgz", + "integrity": "sha512-OfaUIhnyvOkuCPHWMPkJqX++dUaDKsiZWuZqCdU04Z9dNAl2TtZAh7dw2rsZGb57vq6YH3PierNrDfQJTAKYtg==", + "dependencies": { + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "engines": { + "node": ">=14.0.0" } }, - "node_modules/postcss": { - "version": "8.4.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz", - "integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==", - "dev": true, + "node_modules/@smithy/protocol-http": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-2.0.4.tgz", + "integrity": "sha512-I1vCZ/m1U424gA9TXkL/pJ3HlRfujY8+Oj3GfDWcrNiWVmAeyx3CTvXw+yMHp2X01BOOu5fnyAa6JwAn1O+txA==", "dependencies": { - "nanoid": "^3.2.0", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" }, "engines": { - "node": "^10 || ^12 || >=14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "node": ">=14.0.0" } }, - "node_modules/postcss-js": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-3.0.3.tgz", - "integrity": "sha512-gWnoWQXKFw65Hk/mi2+WTQTHdPD5UJdDXZmX073EY/B3BWnYjO4F4t0VneTCnCGQ5E5GsCdMkzPaTXwl3r5dJw==", - "dev": true, + "node_modules/@smithy/querystring-builder": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.0.4.tgz", + "integrity": "sha512-Jc7UPx1pNeisYcABkoo2Pn4kvomy1UI7uxv7R+1W3806KMAKgYHutWmZG01aPHu2XH0zY2RF2KfGiuialsxHvA==", "dependencies": { - "camelcase-css": "^2.0.1", - "postcss": "^8.1.6" + "@smithy/types": "^2.2.1", + "@smithy/util-uri-escape": "^2.0.0", + "tslib": "^2.5.0" }, "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "node": ">=14.0.0" } }, - "node_modules/postcss-load-config": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.1.tgz", - "integrity": "sha512-c/9XYboIbSEUZpiD1UQD0IKiUe8n9WHYV7YFe7X7J+ZwCsEKkUJSFWjS9hBU1RR9THR7jMXst8sxiqP0jjo2mg==", - "dev": true, + "node_modules/@smithy/querystring-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-2.0.4.tgz", + "integrity": "sha512-Uh6+PhGxSo17qe2g/JlyoekvTHKn7dYWfmHqUzPAvkW+dHlc3DNVG3++PV48z33lCo5YDVBBturWQ9N/TKn+EA==", "dependencies": { - "lilconfig": "^2.0.4", - "yaml": "^1.10.2" + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" }, "engines": { - "node": ">= 10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - } + "node": ">=14.0.0" } }, - "node_modules/postcss-nested": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz", - "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==", - "dev": true, + "node_modules/@smithy/service-error-classification": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-2.0.0.tgz", + "integrity": "sha512-2z5Nafy1O0cTf69wKyNjGW/sNVMiqDnb4jgwfMG8ye8KnFJ5qmJpDccwIbJNhXIfbsxTg9SEec2oe1cexhMJvw==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.0.4.tgz", + "integrity": "sha512-091yneupXnSqvAU+vLG7h0g4QRRO6TjulpECXYVU6yW/LiNp7QE533DBpaphmbtI6tTC4EfGrhn35gTa0w+GQg==", "dependencies": { - "postcss-selector-parser": "^6.0.6" + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" }, "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.2.14" + "node": ">=14.0.0" } }, - "node_modules/postcss-selector-parser": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz", - "integrity": "sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ==", - "dev": true, + "node_modules/@smithy/signature-v4": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.0.4.tgz", + "integrity": "sha512-y2xblkS0hb44QJDn9YjPp5aRFYSiI7w0bI3tATE3ybOrII2fppqD0SE3zgvew/B/3rTunuiCW+frTD0W4UYb9Q==", "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" + "@smithy/eventstream-codec": "^2.0.4", + "@smithy/is-array-buffer": "^2.0.0", + "@smithy/types": "^2.2.1", + "@smithy/util-hex-encoding": "^2.0.0", + "@smithy/util-middleware": "^2.0.0", + "@smithy/util-uri-escape": "^2.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.5.0" }, "engines": { - "node": ">=4" + "node": ">=14.0.0" } }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "node_modules/pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", - "dev": true, + "node_modules/@smithy/smithy-client": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.0.4.tgz", + "integrity": "sha512-Dg1dkqyj3jwa03RFs6E4ASmfQ7CjplbGISJIJNSt3F8NfIid2RalbeCMOIHK7VagKh9qngZNyoKxObZC9LB9Lg==", + "dependencies": { + "@smithy/middleware-stack": "^2.0.0", + "@smithy/types": "^2.2.1", + "@smithy/util-stream": "^2.0.4", + "tslib": "^2.5.0" + }, "engines": { - "node": ">= 0.8" + "node": ">=14.0.0" } }, - "node_modules/purgecss": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-4.1.3.tgz", - "integrity": "sha512-99cKy4s+VZoXnPxaoM23e5ABcP851nC2y2GROkkjS8eJaJtlciGavd7iYAw2V84WeBqggZ12l8ef44G99HmTaw==", - "dev": true, + "node_modules/@smithy/types": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.2.1.tgz", + "integrity": "sha512-6nyDOf027ZeJiQVm6PXmLm7dR+hR2YJUkr4VwUniXA8xZUGAu5Mk0zfx2BPFrt+e5YauvlIqQoH0CsrM4tLkfg==", "dependencies": { - "commander": "^8.0.0", - "glob": "^7.1.7", - "postcss": "^8.3.5", - "postcss-selector-parser": "^6.0.6" + "tslib": "^2.5.0" }, - "bin": { - "purgecss": "bin/purgecss.js" + "engines": { + "node": ">=14.0.0" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node_modules/@smithy/url-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-2.0.4.tgz", + "integrity": "sha512-puIQ6+TJpI2AAPw7IGdGG6d2DEcVP5nJqa1VjrxzUcy2Jx7LtGn+gDHY2o9Pc9vQkmoicovTEKgvv7CdqP+0gg==", + "dependencies": { + "@smithy/querystring-parser": "^2.0.4", + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" } }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, + "node_modules/@smithy/util-base64": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.0.0.tgz", + "integrity": "sha512-Zb1E4xx+m5Lud8bbeYi5FkcMJMnn+1WUnJF3qD7rAdXpaL7UjkFQLdmW5fHadoKbdHpwH9vSR8EyTJFHJs++tA==", "dependencies": { - "picomatch": "^2.2.1" + "@smithy/util-buffer-from": "^2.0.0", + "tslib": "^2.5.0" }, "engines": { - "node": ">=8.10.0" + "node": ">=14.0.0" } }, - "node_modules/reduce-css-calc": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz", - "integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==", - "dev": true, + "node_modules/@smithy/util-body-length-browser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-2.0.0.tgz", + "integrity": "sha512-JdDuS4ircJt+FDnaQj88TzZY3+njZ6O+D3uakS32f2VNnDo3vyEuNdBOh/oFd8Df1zSZOuH1HEChk2AOYDezZg==", "dependencies": { - "css-unit-converter": "^1.1.1", - "postcss-value-parser": "^3.3.0" + "tslib": "^2.5.0" } }, - "node_modules/reduce-css-calc/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", - "dev": true, + "node_modules/@smithy/util-body-length-node": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-2.0.0.tgz", + "integrity": "sha512-ZV7Z/WHTMxHJe/xL/56qZwSUcl63/5aaPAGjkfynJm4poILjdD4GmFI+V+YWabh2WJIjwTKZ5PNsuvPQKt93Mg==", "dependencies": { - "is-core-module": "^2.8.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" + "tslib": "^2.5.0" }, - "bin": { - "resolve": "bin/resolve" + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz", + "integrity": "sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==", + "dependencies": { + "@smithy/is-array-buffer": "^2.0.0", + "tslib": "^2.5.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=14.0.0" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, + "node_modules/@smithy/util-config-provider": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.0.0.tgz", + "integrity": "sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg==", + "dependencies": { + "tslib": "^2.5.0" + }, "engines": { - "node": ">=4" + "node": ">=14.0.0" } }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.0.4.tgz", + "integrity": "sha512-wGdnPt4Ng72duUd97HrlqVkq6DKVB/yjaGkSg5n3uuQKzzHjoi3OdjXGumD/VYPHz0dYd7wpLNG2CnMm/nfDrg==", + "dependencies": { + "@smithy/property-provider": "^2.0.4", + "@smithy/types": "^2.2.1", + "bowser": "^2.11.0", + "tslib": "^2.5.0" + }, "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "node": ">= 10.0.0" } }, - "node_modules/rgb-regex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", - "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=", - "dev": true - }, - "node_modules/rgba-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", - "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=", - "dev": true - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.0.4.tgz", + "integrity": "sha512-QMkNcV6x52BeeeIvhvow6UmOu7nP7DXQljY6DKOP/aAokrli53IWTP/kUTd9B0Mp9tbW3WC10O6zaM69xiMNYw==", "dependencies": { - "glob": "^7.1.3" + "@smithy/config-resolver": "^2.0.4", + "@smithy/credential-provider-imds": "^2.0.4", + "@smithy/node-config-provider": "^2.0.4", + "@smithy/property-provider": "^2.0.4", + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">= 10.0.0" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/@smithy/util-hex-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.0.0.tgz", + "integrity": "sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==", "dependencies": { - "queue-microtask": "^1.2.2" + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "dev": true, + "node_modules/@smithy/util-middleware": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.0.0.tgz", + "integrity": "sha512-eCWX4ECuDHn1wuyyDdGdUWnT4OGyIzV0LN1xRttBFMPI9Ff/4heSHVxneyiMtOB//zpXWCha1/SWHJOZstG7kA==", "dependencies": { - "is-arrayish": "^0.3.1" + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "dev": true - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, + "node_modules/@smithy/util-retry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-2.0.0.tgz", + "integrity": "sha512-/dvJ8afrElasuiiIttRJeoS2sy8YXpksQwiM/TcepqdRVp7u4ejd9C4IQURHNjlfPUT7Y6lCDSa2zQJbdHhVTg==", + "dependencies": { + "@smithy/service-error-classification": "^2.0.0", + "tslib": "^2.5.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 14.0.0" } }, - "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==", - "dev": true, + "node_modules/@smithy/util-stream": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.0.4.tgz", + "integrity": "sha512-ZVje79afuv3DB1Ma/g5m/5v9Zda8nA0xNgvE1pOD3EnoTp/Ekch1z20AN6gfVsf7JYWK2VSMVDiqI9N8Ua4wbg==", "dependencies": { - "has-flag": "^4.0.0" + "@smithy/fetch-http-handler": "^2.0.4", + "@smithy/node-http-handler": "^2.0.4", + "@smithy/types": "^2.2.1", + "@smithy/util-base64": "^2.0.0", + "@smithy/util-buffer-from": "^2.0.0", + "@smithy/util-hex-encoding": "^2.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.5.0" }, "engines": { - "node": ">=8" + "node": ">=14.0.0" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" + "node_modules/@smithy/util-uri-escape": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.0.0.tgz", + "integrity": "sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw==", + "dependencies": { + "tslib": "^2.5.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=14.0.0" } }, - "node_modules/tailwindcss": { - "version": "2.2.19", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-2.2.19.tgz", - "integrity": "sha512-6Ui7JSVtXadtTUo2NtkBBacobzWiQYVjYW0ZnKaP9S1ZCKQ0w7KVNz+YSDI/j7O7KCMHbOkz94ZMQhbT9pOqjw==", - "dev": true, + "node_modules/@smithy/util-utf8": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.0.0.tgz", + "integrity": "sha512-rctU1VkziY84n5OXe3bPNpKR001ZCME2JCaBBFgtiM2hfKbHFudc/BkMuPab8hRbLd0j3vbnBTTZ1igBf0wgiQ==", "dependencies": { - "arg": "^5.0.1", - "bytes": "^3.0.0", - "chalk": "^4.1.2", - "chokidar": "^3.5.2", - "color": "^4.0.1", - "cosmiconfig": "^7.0.1", - "detective": "^5.2.0", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.2.7", - "fs-extra": "^10.0.0", - "glob-parent": "^6.0.1", - "html-tags": "^3.1.0", - "is-color-stop": "^1.1.0", - "is-glob": "^4.0.1", - "lodash": "^4.17.21", - "lodash.topath": "^4.5.2", - "modern-normalize": "^1.1.0", - "node-emoji": "^1.11.0", - "normalize-path": "^3.0.0", - "object-hash": "^2.2.0", - "postcss-js": "^3.0.3", - "postcss-load-config": "^3.1.0", - "postcss-nested": "5.0.6", - "postcss-selector-parser": "^6.0.6", - "postcss-value-parser": "^4.1.0", - "pretty-hrtime": "^1.0.3", - "purgecss": "^4.0.3", - "quick-lru": "^5.1.1", - "reduce-css-calc": "^2.1.8", - "resolve": "^1.20.0", - "tmp": "^0.2.1" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" + "@smithy/util-buffer-from": "^2.0.0", + "tslib": "^2.5.0" }, "engines": { - "node": ">=12.13.0" - }, - "peerDependencies": { - "autoprefixer": "^10.0.2", - "postcss": "^8.0.9" + "node": ">=14.0.0" } }, - "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, + "node_modules/@smithy/util-waiter": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-2.0.4.tgz", + "integrity": "sha512-NAzHgewL+sIJw9vlgR4m8btJiu1u0vuQRNRT7Bd5B66h02deFMmOaw1zeGePORZa7zyUwNZ2J5ZPkKzq4ced7Q==", "dependencies": { - "rimraf": "^3.0.0" + "@smithy/abort-controller": "^2.0.4", + "@smithy/types": "^2.2.1", + "tslib": "^2.5.0" }, "engines": { - "node": ">=8.17.0" + "node": ">=14.0.0" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/@swc/helpers": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz", + "integrity": "sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/forms": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.5.tgz", + "integrity": "sha512-03sXK1DcPt44GZ0Yg6AcAfQln89IKdbE79g2OwoKqBm1ukaadLO2AH3EiB3mXHeQnxa3tzm7eE0x7INXSjbuug==", "dev": true, "dependencies": { - "is-number": "^7.0.0" + "mini-svg-data-uri": "^1.2.3" }, - "engines": { - "node": ">=8.0" + "peerDependencies": { + "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1" } }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" + "node_modules/@theguild/remark-mermaid": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@theguild/remark-mermaid/-/remark-mermaid-0.0.3.tgz", + "integrity": "sha512-fccVR6o4UPUztrBjdUhM4ahwx+X7YHhoxsUoXv2vI07vz4dq+I03Ot0SjuZzDA/H7engxcb8ZxzCUEkZgGr/2g==", + "dependencies": { + "mermaid": "^10.2.2", + "unist-util-visit": "^4.1.2" + }, + "peerDependencies": { + "react": "^18.2.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "node_modules/@theguild/remark-mermaid/node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "node_modules/@types/acorn": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz", + "integrity": "sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==", + "dependencies": { + "@types/estree": "*" + } }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "engines": { - "node": ">=0.4" + "node_modules/@types/debug": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", + "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", + "dependencies": { + "@types/ms": "*" } }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" + "node_modules/@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.0.tgz", + "integrity": "sha512-3qvGd0z8F2ENTGr/GG1yViqfiKmRfrXVx5sJyHGFu3z7m5g5utCQtGp/g29JnjflhtQJBv1WDQukHiT58xPcYQ==", + "dependencies": { + "@types/estree": "*" } - } - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.7" + }, + "node_modules/@types/hast": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", + "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", + "dependencies": { + "@types/unist": "*" } }, - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true + "node_modules/@types/js-yaml": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", + "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==" }, - "@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } + "node_modules/@types/katex": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.14.0.tgz", + "integrity": "sha512-+2FW2CcT0K3P+JMR8YG846bmDwplKUTsWgT2ENwdQ1UdVfRk3GQrh6Mi4sTopy30gI8Uau5CEqHTDZ6YvWIUPA==" }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "node_modules/@types/mdast": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.11.tgz", + "integrity": "sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw==", + "dependencies": { + "@types/unist": "*" } }, - "@nodelib/fs.stat": { + "node_modules/@types/mdx": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.5.tgz", + "integrity": "sha512-76CqzuD6Q7LC+AtbPqrvD9AqsN0k8bsYo2bM2J8pmNldP1aIPAbzUQ7QbobyXL4eLr1wK5x8FZFe8eF/ubRuBg==" + }, + "node_modules/@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "node_modules/@types/react": { + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.14.tgz", + "integrity": "sha512-A0zjq+QN/O0Kpe30hA1GidzyFjatVvrpIvWLxD+xv67Vt91TWWgco9IvrJBkeyHm1trGaFS/FSGqPlhyeZRm0g==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" } }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true + "node_modules/@types/scheduler": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", + "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true + "node_modules/@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" }, - "acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "dev": true, - "requires": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" } }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" + "node_modules/algoliasearch": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.20.0.tgz", + "integrity": "sha512-y+UHEjnOItoNy0bYO+WWmLWBlPwDjKHW6mNHrPi0NkuhpQOOEbrkwQH/wgKFDLh7qlKjzoKeiRtlpewDPDG23g==", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.20.0", + "@algolia/cache-common": "4.20.0", + "@algolia/cache-in-memory": "4.20.0", + "@algolia/client-account": "4.20.0", + "@algolia/client-analytics": "4.20.0", + "@algolia/client-common": "4.20.0", + "@algolia/client-personalization": "4.20.0", + "@algolia/client-search": "4.20.0", + "@algolia/logger-common": "4.20.0", + "@algolia/logger-console": "4.20.0", + "@algolia/requester-browser-xhr": "4.20.0", + "@algolia/requester-common": "4.20.0", + "@algolia/requester-node-http": "4.20.0", + "@algolia/transporter": "4.20.0" + } + }, + "node_modules/ansi-sequence-parser": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.0.tgz", + "integrity": "sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ==" + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" } }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "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==", + "dev": true + }, + "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==", "dev": true, - "requires": { + "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" } }, - "arg": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.1.tgz", - "integrity": "sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==", + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "dev": true }, - "autoprefixer": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.2.tgz", - "integrity": "sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ==", + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/astring": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.8.6.tgz", + "integrity": "sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.14", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", + "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", "dev": true, - "peer": true, - "requires": { - "browserslist": "^4.19.1", - "caniuse-lite": "^1.0.30001297", - "fraction.js": "^4.1.2", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + ], + "dependencies": { + "browserslist": "^4.21.5", + "caniuse-lite": "^1.0.30001464", + "fraction.js": "^4.2.0", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/b4a": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", + "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==" + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "balanced-match": { + "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "brace-expansion": { + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" + }, + "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "requires": { + "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "braces": { + "node_modules/braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { + "dependencies": { "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" } }, - "browserslist": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", - "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", + "node_modules/browserslist": { + "version": "4.21.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", + "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", "dev": true, - "peer": true, - "requires": { - "caniuse-lite": "^1.0.30001286", - "electron-to-chromium": "^1.4.17", - "escalade": "^3.1.1", - "node-releases": "^2.0.1", - "picocolors": "^1.0.0" + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001503", + "electron-to-chromium": "^1.4.431", + "node-releases": "^2.0.12", + "update-browserslist-db": "^1.0.11" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", "dev": true }, - "camelcase-css": { + "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001307", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001307.tgz", - "integrity": "sha512-+MXEMczJ4FuxJAUp0jvAl6Df0NI/OfW1RWEE61eSmzS7hw6lz4IKutbhbXendwq8BljfFuHtu26VWsg4afQ7Ng==", "dev": true, - "peer": true + "engines": { + "node": ">= 6" + } }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "node_modules/caniuse-lite": { + "version": "1.0.30001514", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001514.tgz", + "integrity": "sha512-ENcIpYBmwAAOm/V2cXgM7rZUrKKaqisZl4ZAI520FIkqGXUxJjmaIssbRW5HVVR5tyV6ygTLIm15aU8LUmQSaQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "dependencies": { + "ansi-styles": "^3.1.0", + "escape-string-regexp": "^1.0.5", + "supports-color": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "chokidar": { + "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", - "fsevents": "~2.3.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, + "node_modules/clipboardy": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-1.2.2.tgz", + "integrity": "sha512-16KrBOV7bHmHdxcQiCvfUFYVFyEah4FI8vYT1Fr7CGSA4G+xBWMEfUEQJS1hxeHGtI9ju1Bzs9uXSbj5HZKArw==", "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } + "arch": "^2.1.0", + "execa": "^0.8.0" + }, + "engines": { + "node": ">=4" } }, - "color": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.0.tgz", - "integrity": "sha512-hHTcrbvEnGjC7WBMk6ibQWFVDgEFTVmjrz2Q5HlU6ltwxv0JJN2Z8I7uRbWeQLF04dikxs8zgyZkazRJvSMtyQ==", - "dev": true, - "requires": { + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" } }, - "color-convert": { + "node_modules/color/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { + "dependencies": { "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "color-name": { + "node_modules/color/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "color-string": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.0.tgz", - "integrity": "sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==", - "dev": true, - "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "commander": { + "node_modules/commander": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true + "engines": { + "node": ">= 12" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.0.3.tgz", + "integrity": "sha512-nadqwNxghAGTamwIqQSG433W6OADZx2vCo3UXHNrzTRHK/htu+7+L0zhjEoaeaQVNAi3YgqWDv8+tzf0hRfR+A==" }, - "concat-map": { + "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", - "dev": true, - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - } - }, - "css-color-names": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", - "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", - "dev": true + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "dependencies": { + "layout-base": "^1.0.0" + } }, - "css-unit-converter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz", - "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==", - "dev": true + "node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } }, - "cssesc": { + "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true - }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true - }, - "detective": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", - "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", "dev": true, - "requires": { - "acorn-node": "^1.6.1", - "defined": "^1.0.0", - "minimist": "^1.1.1" + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" } }, - "didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true - }, - "dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true + "node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, - "electron-to-chromium": { - "version": "1.4.65", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.65.tgz", - "integrity": "sha512-0/d8Skk8sW3FxXP0Dd6MnBlrwx7Qo9cqQec3BlIAlvKnrmS3pHsIbaroEi+nd0kZkGpQ6apMEre7xndzjlEnLw==", - "dev": true, - "peer": true + "node_modules/cytoscape": { + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.25.0.tgz", + "integrity": "sha512-7MW3Iz57mCUo6JQCho6CmPBCbTlJr7LzyEtIkutG255HLVd4XuBg2I9BkTZLI/e4HoaOB/BiAzXuQybQ95+r9Q==", + "dependencies": { + "heap": "^0.2.6", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=0.10" + } }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" } }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "peer": true + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==" + }, + "node_modules/d3": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", + "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.10.tgz", + "integrity": "sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==", + "dependencies": { + "d3": "^7.8.2", + "lodash-es": "^4.17.21" + } + }, + "node_modules/dayjs": { + "version": "1.11.9", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", + "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/delaunator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", + "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", + "dependencies": { + "robust-predicates": "^3.0.0" + } + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/dompurify": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.3.tgz", + "integrity": "sha512-axQ9zieHLnAnHh0sfAamKYiqXMJAVwu+LM/alQ7WDagoWessyWvMSFyW65CqF3owufNu8HBcE4cM2Vflu7YWcQ==" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.454", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.454.tgz", + "integrity": "sha512-pmf1rbAStw8UEQ0sr2cdJtWl48ZMuPD9Sto8HVQOq9vx9j2WgDEN6lYoaqFvqEHYOmGA9oRGn7LqWI9ta0YugQ==", + "dev": true + }, + "node_modules/elkjs": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.8.2.tgz", + "integrity": "sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==" + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estree-util-attach-comments": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-2.1.1.tgz", + "integrity": "sha512-+5Ba/xGGS6mnwFbXIuQiDPTbuTxuMCooq3arVv7gPZtYpjp+VXH/NkHAP35OOefPhNG/UGqU3vt/LTABwcHX0w==", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-2.2.2.tgz", + "integrity": "sha512-m56vOXcOBuaF+Igpb9OPAy7f9w9OIkb5yhjsZuaPm7HoGi4oTOQi0h2+yZ+AtKklYFZ+rPC4n0wYCJCEU1ONqg==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "estree-util-is-identifier-name": "^2.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-2.1.0.tgz", + "integrity": "sha512-bEN9VHRyXAUOjkKVQVvArFym08BTWB0aJPppZZr0UNyAqWsLaVfAqP7hbaTJjzHifmB5ebnR8Wm7r7yGN/HonQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-1.2.0.tgz", + "integrity": "sha512-IzU74r1PK5IMMGZXUVZbmiu4A1uhiPgW5hm1GjcOfr4ZzHaMPpLNJjR7HjXiIOzi25nZDrgFTobHTkV5Q6ITjA==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-value-to-estree": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-1.3.0.tgz", + "integrity": "sha512-Y+ughcF9jSUJvncXwqRageavjrNPAI+1M/L3BI3PyLp1nmgYTGUXU6t5z1Y7OWuThoDdhPME07bQU+d5LxdJqw==", + "dependencies": { + "is-plain-obj": "^3.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/estree-util-visit": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-1.2.1.tgz", + "integrity": "sha512-xbgqcrkIVbIG+lI/gzbvd9SGTJL4zqJKBFttUl5pP27KhAjtMKbX/mQXJ7qgyXpMgVy/zvpm0xoQQaGL8OloOw==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/execa": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz", + "integrity": "sha512-zDWS+Rb1E8BlqqhALSt9kUhss8Qq4nN3iof3gsOdyINksElaPyNBtKUMTR62qhvgVWR0CqCX7sdnKe4MnUbFEA==", + "dependencies": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + }, + "node_modules/fast-glob": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz", + "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-xml-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz", + "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==", + "funding": [ + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + }, + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flexsearch": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/flexsearch/-/flexsearch-0.7.31.tgz", + "integrity": "sha512-XGozTsMPYkm+6b5QL3Z9wQcJjNYxp0CYn3U1gO7dwD6PAqU1SVWZxI9CCg3z+ml3YfqdPnrBehaBrnH2AGKbNA==" + }, + "node_modules/focus-visible": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/focus-visible/-/focus-visible-5.2.0.tgz", + "integrity": "sha512-Rwix9pBtC1Nuy5wysTmKy+UjbDJpIfg8eHjw0rjZ1mX4GNLz1Bmd16uDpI3Gk1i70Fgcs8Csg2lPm8HULFg9DQ==" + }, + "node_modules/fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/git-up": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-7.0.0.tgz", + "integrity": "sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==", + "dependencies": { + "is-ssh": "^1.4.0", + "parse-url": "^8.1.0" + } + }, + "node_modules/git-url-parse": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.0.tgz", + "integrity": "sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA==", + "dependencies": { + "git-up": "^7.0.0" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" + }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "integrity": "sha512-Iozmtbqv0noj0uDDqoL0zNq0VBEfK2YFoMAZoxJe4cwphvLR+JskfF30QhXHOR4m3KrE6NLRYw+U9MRXvifyig==", + "dev": true + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha512-P+1n3MnwjR/Epg9BBo1KT8qbye2g2Ou4sFumihwt6I4tsUX7jnLcX4BTOSKg/B1ZrIYMN9FcEnG4x5a7NB8Eng==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hash-obj": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hash-obj/-/hash-obj-4.0.0.tgz", + "integrity": "sha512-FwO1BUVWkyHasWDW4S8o0ssQXjvyghLV2rfVhnN36b2bbcj45eGiuzdn9XOvOpjV3TKQD7Gm2BWNXdE9V4KKYg==", + "dependencies": { + "is-obj": "^3.0.0", + "sort-keys": "^5.0.0", + "type-fest": "^1.0.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hast-util-from-dom": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-4.2.0.tgz", + "integrity": "sha512-t1RJW/OpJbCAJQeKi3Qrj1cAOLA0+av/iPFori112+0X7R3wng+jxLA+kXec8K4szqPRGI8vPxbbpEYvvpwaeQ==", + "dependencies": { + "hastscript": "^7.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-1.0.2.tgz", + "integrity": "sha512-LhrTA2gfCbLOGJq2u/asp4kwuG0y6NhWTXiPKP+n0qNukKy7hc10whqqCFfyvIA1Q5U5d0sp9HhNim9gglEH4A==", + "dependencies": { + "@types/hast": "^2.0.0", + "hast-util-from-parse5": "^7.0.0", + "parse5": "^7.0.0", + "vfile": "^5.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-1.0.0.tgz", + "integrity": "sha512-Yu480AKeOEN/+l5LA674a+7BmIvtDj24GvOt7MtQWuhzUwlaaRWdEPXAh3Qm5vhuthpAipFb2vTetKXWOjmTvw==", + "dependencies": { + "@types/hast": "^2.0.0", + "hast-util-from-dom": "^4.0.0", + "hast-util-from-html": "^1.0.0", + "unist-util-remove-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz", + "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "hastscript": "^7.0.0", + "property-information": "^6.0.0", + "vfile": "^5.0.0", + "vfile-location": "^4.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.3.tgz", + "integrity": "sha512-O1bKah6mhgEq2WtVMk+Ta5K7pPMqsBBlmzysLdcwKVrqzZQ0CHqUPiIVspNhAG1rvxpvJjtGee17XfauZYKqVA==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-2.3.3.tgz", + "integrity": "sha512-ihhPIUPxN0v0w6M5+IiAZZrn0LH2uZomeWwhn7uP7avZC6TE7lIiEh2yBMPr5+zi1aUCXq6VoYRgs2Bw9xmycQ==", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "estree-util-attach-comments": "^2.0.0", + "estree-util-is-identifier-name": "^2.0.0", + "hast-util-whitespace": "^2.0.0", + "mdast-util-mdx-expression": "^1.0.0", + "mdast-util-mdxjs-esm": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^0.4.1", + "unist-util-position": "^4.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-3.1.2.tgz", + "integrity": "sha512-tcllLfp23dJJ+ju5wCCZHVpzsQQ43+moJbqVX3jNWPB7z/KFC4FyZD6R7y94cHL6MQ33YtMZL8Z0aIXXI4XFTw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "hast-util-is-element": "^2.0.0", + "unist-util-find-after": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/heap": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", + "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==" + }, + "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==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/intersection-observer": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.12.2.tgz", + "integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==" + }, + "node_modules/is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz", + "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-reference": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.1.tgz", + "integrity": "sha512-baJJdQLiYaJdvFbJqXrcGv3WU3QCzBlUcI5QhbesIm6/xPsvmO+2CDoi/GMOFBQEQm+PXkwOPrp9KK5ozZsp2w==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-ssh": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz", + "integrity": "sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==", + "dependencies": { + "protocols": "^2.0.1" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/jiti": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz", + "integrity": "sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-ftuDnJbcbOckGY11OO+zg3OofESlbR5DRl2cmN8HeWeeFIV7wTXvAOx8kEjZjobhA+9wh2fbKeO6cdcA9Mnovg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/khroma": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.0.0.tgz", + "integrity": "sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g==" + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==" + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/markdown-extensions": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz", + "integrity": "sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/markdown-table": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", + "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/match-sorter": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz", + "integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "remove-accents": "0.4.2" + } + }, + "node_modules/mdast-util-definitions": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", + "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-definitions/node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.2.tgz", + "integrity": "sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==", + "dependencies": { + "@types/mdast": "^3.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz", + "integrity": "sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==", + "dependencies": { + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-gfm-autolink-literal": "^1.0.0", + "mdast-util-gfm-footnote": "^1.0.0", + "mdast-util-gfm-strikethrough": "^1.0.0", + "mdast-util-gfm-table": "^1.0.0", + "mdast-util-gfm-task-list-item": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.3.tgz", + "integrity": "sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==", + "dependencies": { + "@types/mdast": "^3.0.0", + "ccount": "^2.0.0", + "mdast-util-find-and-replace": "^2.0.0", + "micromark-util-character": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz", + "integrity": "sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0", + "micromark-util-normalize-identifier": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz", + "integrity": "sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz", + "integrity": "sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==", + "dependencies": { + "@types/mdast": "^3.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz", + "integrity": "sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-math": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-2.0.2.tgz", + "integrity": "sha512-8gmkKVp9v6+Tgjtq6SYx9kGPpTf6FVYRa53/DLh479aldR9AyP48qeVOgNZ5X7QUK7nOy4yw7vg6mbiGcs9jWQ==", + "dependencies": { + "@types/mdast": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-2.0.1.tgz", + "integrity": "sha512-38w5y+r8nyKlGvNjSEqWrhG0w5PmnRA+wnBvm+ulYCct7nsGYhFVb0lljS9bQav4psDAS1eGkP2LMVcZBi/aqw==", + "dependencies": { + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-mdx-expression": "^1.0.0", + "mdast-util-mdx-jsx": "^2.0.0", + "mdast-util-mdxjs-esm": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-1.3.2.tgz", + "integrity": "sha512-xIPmR5ReJDu/DHH1OoIT1HkuybIfRGYRywC+gJtI7qHjCJp/M9jrmBEJW22O8lskDWm562BX2W8TiAwRTb0rKA==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-2.1.4.tgz", + "integrity": "sha512-DtMn9CmVhVzZx3f+optVDF8yFgQVt7FghCRNdlIaS3X5Bnym3hZwPbg/XW86vdpKjlc1PVj26SpnLGeJBXD3JA==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "ccount": "^2.0.0", + "mdast-util-from-markdown": "^1.1.0", + "mdast-util-to-markdown": "^1.3.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-remove-position": "^4.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-1.3.1.tgz", + "integrity": "sha512-SXqglS0HrEvSdUEfoXFtcg7DRl7S2cwOXc7jkuusG472Mmjag34DUDeOJUZtl+BVnyeO1frIgVpHlNRWc2gk/w==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==", + "dependencies": { + "@types/mdast": "^3.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", + "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-definitions": "^5.0.0", + "micromark-util-sanitize-uri": "^1.1.0", + "trim-lines": "^3.0.0", + "unist-util-generated": "^2.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast/node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz", + "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^3.0.0", + "mdast-util-to-string": "^3.0.0", + "micromark-util-decode-string": "^1.0.0", + "unist-util-visit": "^4.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "dependencies": { + "@types/mdast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/mermaid": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.2.4.tgz", + "integrity": "sha512-zHGjEI7lBvWZX+PQYmlhSA2p40OzW6QbGodTCSzDeVpqaTnyAC+2sRGqrpXO+uQk3CnoeClHQPraQUMStdqy2g==", + "dependencies": { + "@braintree/sanitize-url": "^6.0.2", + "cytoscape": "^3.23.0", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.1.0", + "d3": "^7.4.0", + "dagre-d3-es": "7.0.10", + "dayjs": "^1.11.7", + "dompurify": "3.0.3", + "elkjs": "^0.8.2", + "khroma": "^2.0.0", + "lodash-es": "^4.17.21", + "mdast-util-from-markdown": "^1.3.0", + "non-layered-tidy-tree-layout": "^2.0.2", + "stylis": "^4.1.3", + "ts-dedent": "^2.2.0", + "uuid": "^9.0.0", + "web-worker": "^1.2.0" + } + }, + "node_modules/micromark": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.3.tgz", + "integrity": "sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^1.0.0", + "micromark-extension-gfm-footnote": "^1.0.0", + "micromark-extension-gfm-strikethrough": "^1.0.0", + "micromark-extension-gfm-table": "^1.0.0", + "micromark-extension-gfm-tagfilter": "^1.0.0", + "micromark-extension-gfm-task-list-item": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.5.tgz", + "integrity": "sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.2.tgz", + "integrity": "sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==", + "dependencies": { + "micromark-core-commonmark": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.7.tgz", + "integrity": "sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==", + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.7.tgz", + "integrity": "sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz", + "integrity": "sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==", + "dependencies": { + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz", + "integrity": "sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-2.1.2.tgz", + "integrity": "sha512-es0CcOV89VNS9wFmyn+wyFTKweXGW4CEvdaAca6SWRWPyYCbBisnjaHLjWO4Nszuiud84jCpkHsqAJoa768Pvg==", + "dependencies": { + "@types/katex": "^0.16.0", + "katex": "^0.16.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math/node_modules/@types/katex": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.0.tgz", + "integrity": "sha512-hz+S3nV6Mym5xPbT9fnO8dDhBFQguMYpY0Ipxv06JMi1ORgnEM4M1ymWDUhUNer3ElLmT583opRo4RzxKmh9jw==" + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-1.0.8.tgz", + "integrity": "sha512-zZpeQtc5wfWKdzDsHRBY003H2Smg+PUi2REhqgIhdzAa5xonhP03FcXxqFSerFiNUr5AWmHpaNPQTBVOS4lrXw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/estree": "^1.0.0", + "micromark-factory-mdx-expression": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-events-to-acorn": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-1.0.5.tgz", + "integrity": "sha512-gPH+9ZdmDflbu19Xkb8+gheqEDqkSpdCEubQyxuz/Hn8DOXiXvrXeikOoBA71+e8Pfi0/UYmU3wW3H58kr7akA==", + "dependencies": { + "@types/acorn": "^4.0.0", + "@types/estree": "^1.0.0", + "estree-util-is-identifier-name": "^2.0.0", + "micromark-factory-mdx-expression": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-1.0.1.tgz", + "integrity": "sha512-7MSuj2S7xjOQXAjjkbjBsHkMtb+mDGVW6uI2dBL9snOBCbZmoNgDAeZ0nSn9j3T42UE/g2xVNMn18PJxZvkBEA==", + "dependencies": { + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-1.0.1.tgz", + "integrity": "sha512-7YA7hF6i5eKOfFUzZ+0z6avRG52GpWR8DL+kN47y3f2KhxbBZMhmxe7auOeaTBrW2DenbbZTf1ea9tA2hDpC2Q==", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^1.0.0", + "micromark-extension-mdx-jsx": "^1.0.0", + "micromark-extension-mdx-md": "^1.0.0", + "micromark-extension-mdxjs-esm": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-1.0.5.tgz", + "integrity": "sha512-xNRBw4aoURcyz/S69B19WnZAkWJMxHMT5hE36GtDAyhoyn/8TuAeqjFJQlwk+MKQsUD7b3l7kFX+vlfVWgcX1w==", + "dependencies": { + "@types/estree": "^1.0.0", + "micromark-core-commonmark": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-events-to-acorn": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-position-from-estree": "^1.1.0", + "uvu": "^0.5.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-1.0.9.tgz", + "integrity": "sha512-jGIWzSmNfdnkJq05c7b0+Wv0Kfz3NJ3N4cBjnbO4zjXIlxJr+f8lk+5ZmwFvqdAbUy2q6B5rCY//g0QAAaXDWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/estree": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-events-to-acorn": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-position-from-estree": "^1.0.0", + "uvu": "^0.5.0", + "vfile-message": "^3.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-1.2.3.tgz", + "integrity": "sha512-ij4X7Wuc4fED6UoLWkmo0xJQhsktfNh1J0m8g4PbIMPlx+ek/4YdW5mvbye8z/aZvAPUoxgXHrwVlXAPKMRp1w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/acorn": "^4.0.0", + "@types/estree": "^1.0.0", + "@types/unist": "^2.0.0", + "estree-util-visit": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0", + "vfile-message": "^3.0.0" + } + }, + "node_modules/micromark-util-html-tag-name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "dev": true, + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, + "node_modules/next": { + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/next/-/next-13.4.9.tgz", + "integrity": "sha512-vtefFm/BWIi/eWOqf1GsmKG3cjKw1k3LjuefKRcL3iiLl3zWzFdPG3as6xtxrGO6gwTzzaO1ktL4oiHt/uvTjA==", + "dependencies": { + "@next/env": "13.4.9", + "@swc/helpers": "0.5.1", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001406", + "postcss": "8.4.14", + "styled-jsx": "5.1.1", + "watchpack": "2.4.0", + "zod": "3.21.4" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=16.8.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "13.4.9", + "@next/swc-darwin-x64": "13.4.9", + "@next/swc-linux-arm64-gnu": "13.4.9", + "@next/swc-linux-arm64-musl": "13.4.9", + "@next/swc-linux-x64-gnu": "13.4.9", + "@next/swc-linux-x64-musl": "13.4.9", + "@next/swc-win32-arm64-msvc": "13.4.9", + "@next/swc-win32-ia32-msvc": "13.4.9", + "@next/swc-win32-x64-msvc": "13.4.9" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "fibers": ">= 3.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "fibers": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next-mdx-remote": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/next-mdx-remote/-/next-mdx-remote-4.4.1.tgz", + "integrity": "sha512-1BvyXaIou6xy3XoNF4yaMZUCb6vD2GTAa5ciOa6WoO+gAUTYsb1K4rI/HSC2ogAWLrb/7VSV52skz07vOzmqIQ==", + "dependencies": { + "@mdx-js/mdx": "^2.2.1", + "@mdx-js/react": "^2.2.1", + "vfile": "^5.3.0", + "vfile-matter": "^3.0.1" + }, + "engines": { + "node": ">=14", + "npm": ">=7" + }, + "peerDependencies": { + "react": ">=16.x <=18.x", + "react-dom": ">=16.x <=18.x" + } + }, + "node_modules/next-plausible": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/next-plausible/-/next-plausible-3.11.1.tgz", + "integrity": "sha512-Gd+QU+XflVTx65yJ2cNZyrOQLrpz3uKjGLEG4ls+CIVqK3yjsVGCMsEWJcvv1LAeVfHUQtCyp1xyAltLn5Odcg==", + "funding": { + "url": "https://github.com/4lejandrito/next-plausible?sponsor=1" + }, + "peerDependencies": { + "next": "^11.1.0 || ^12.0.0 || ^13.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/next-seo": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/next-seo/-/next-seo-6.1.0.tgz", + "integrity": "sha512-iMBpFoJsR5zWhguHJvsoBDxDSmdYTHtnVPB1ij+CD0NReQCP78ZxxbdL9qkKIf4oEuZEqZkrjAQLB0bkII7RYA==", + "peerDependencies": { + "next": "^8.1.1-canary.54 || >=9.0.0", + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/next-sitemap": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/next-sitemap/-/next-sitemap-4.2.3.tgz", + "integrity": "sha512-vjdCxeDuWDzldhCnyFCQipw5bfpl4HmZA7uoo3GAaYGjGgfL4Cxb1CiztPuWGmS+auYs7/8OekRS8C2cjdAsjQ==", + "funding": [ + { + "url": "https://github.com/iamvishnusankar/next-sitemap.git" + } + ], + "dependencies": { + "@corex/deepmerge": "^4.0.43", + "@next/env": "^13.4.3", + "fast-glob": "^3.2.12", + "minimist": "^1.2.8" + }, + "bin": { + "next-sitemap": "bin/next-sitemap.mjs", + "next-sitemap-cjs": "bin/next-sitemap.cjs" + }, + "engines": { + "node": ">=14.18" + }, + "peerDependencies": { + "next": "*" + } + }, + "node_modules/next-themes": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz", + "integrity": "sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==", + "peerDependencies": { + "next": "*", + "react": "*", + "react-dom": "*" + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", + "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/nextra": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/nextra/-/nextra-2.9.0.tgz", + "integrity": "sha512-GwYFwuS0UzCXAUVvs9sVAFe4AQeZ0VACbqW8rzw2goxHvpTXAWbIdXShPGz+MEPB8aAckfHy1p39XbfcVU3cRA==", + "dependencies": { + "@headlessui/react": "^1.7.10", + "@mdx-js/mdx": "^2.3.0", + "@mdx-js/react": "^2.3.0", + "@napi-rs/simple-git": "^0.1.8", + "@theguild/remark-mermaid": "^0.0.3", + "clsx": "^1.2.1", + "github-slugger": "^2.0.0", + "graceful-fs": "^4.2.11", + "gray-matter": "^4.0.3", + "katex": "^0.16.7", + "lodash.get": "^4.4.2", + "next-mdx-remote": "^4.2.1", + "p-limit": "^3.1.0", + "rehype-katex": "^6.0.3", + "rehype-pretty-code": "0.9.11", + "remark-gfm": "^3.0.1", + "remark-math": "^5.1.1", + "remark-reading-time": "^2.0.1", + "shiki": "^0.14.2", + "slash": "^3.0.0", + "title": "^3.5.3", + "unist-util-remove": "^4.0.0", + "unist-util-visit": "^5.0.0", + "zod": "^3.20.2" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "next": ">=9.5.3", + "react": ">=16.13.1", + "react-dom": ">=16.13.1" + } + }, + "node_modules/nextra-theme-docs": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/nextra-theme-docs/-/nextra-theme-docs-2.9.0.tgz", + "integrity": "sha512-Gbjx5atl63JKWOEV2hAefP4X0wmJoLtFX4NekvNu9Y9fAdvtFdW0O2p0f8ISMS1JJgAl1ZmXm411J7rHnRnhmg==", + "dependencies": { + "@headlessui/react": "^1.7.10", + "@popperjs/core": "^2.11.6", + "clsx": "^1.2.1", + "flexsearch": "^0.7.21", + "focus-visible": "^5.2.0", + "git-url-parse": "^13.1.0", + "intersection-observer": "^0.12.2", + "match-sorter": "^6.3.1", + "next-seo": "^6.0.0", + "next-themes": "^0.2.1", + "scroll-into-view-if-needed": "^3.0.0", + "zod": "^3.20.2" + }, + "peerDependencies": { + "next": ">=9.5.3", + "nextra": "2.9.0", + "react": ">=16.13.1", + "react-dom": ">=16.13.1" + } + }, + "node_modules/node-abi": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.49.0.tgz", + "integrity": "sha512-ji8IK8VT2zAQv9BeOqwnpuvJnCivxPCe2HNiPe8P1z1SDhqEFpm7GqctqTWkujb8mLfZ1PWDrjMeiq6l9TN7fA==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==" + }, + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true + }, + "node_modules/non-layered-tidy-tree-layout": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz", + "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-entities": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", + "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-numeric-range": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" + }, + "node_modules/parse-path": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", + "integrity": "sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==", + "dependencies": { + "protocols": "^2.0.0" + } + }, + "node_modules/parse-url": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz", + "integrity": "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==", + "dependencies": { + "parse-path": "^7.0.0" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/periscopic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", + "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^3.0.0", + "is-reference": "^3.0.0" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.4.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.25.tgz", + "integrity": "sha512-7taJ/8t2av0Z+sQEvNzCkpDynl0tX3uJMCODi6nT3PfASC7dYCWV9aQ+uiCf+KBD4SEFcu+GvJdGdwzQ6OSjCw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "dev": true, + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^2.1.1" + }, + "engines": { + "node": ">= 14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, - "fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "node_modules/prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prebuild-install/node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/prebuild-install/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/property-information": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz", + "integrity": "sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/protocols": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz", + "integrity": "sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==" + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-enhanced": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/readdir-enhanced/-/readdir-enhanced-1.5.2.tgz", + "integrity": "sha512-oncAoS9LLjy/+DeZfSAdZBI/iFJGcPCOp44RPFI6FIMHuxt5CC5P0cUZ9mET+EZB9ONhcEvAids/lVRkj0sTHw==", + "dev": true, + "dependencies": { + "call-me-maybe": "^1.0.1", + "es6-promise": "^4.1.0", + "glob-to-regexp": "^0.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reading-time": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz", + "integrity": "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==" + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/rehype-katex": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-6.0.3.tgz", + "integrity": "sha512-ByZlRwRUcWegNbF70CVRm2h/7xy7jQ3R9LaY4VVSvjnoVWwWVhNL60DiZsBpC5tSzYQOCvDbzncIpIjPZWodZA==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/katex": "^0.14.0", + "hast-util-from-html-isomorphic": "^1.0.0", + "hast-util-to-text": "^3.1.0", + "katex": "^0.16.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-katex/node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-pretty-code": { + "version": "0.9.11", + "resolved": "https://registry.npmjs.org/rehype-pretty-code/-/rehype-pretty-code-0.9.11.tgz", + "integrity": "sha512-Eq90eCYXQJISktfRZ8PPtwc5SUyH6fJcxS8XOMnHPUQZBtC6RYo67gGlley9X2nR8vlniPj0/7oCDEYHKQa/oA==", + "dependencies": { + "@types/hast": "^2.0.0", + "hash-obj": "^4.0.0", + "parse-numeric-range": "^1.3.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "shiki": "*" + } + }, + "node_modules/remark-gfm": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz", + "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-gfm": "^2.0.0", + "micromark-extension-gfm": "^2.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-math": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-5.1.1.tgz", + "integrity": "sha512-cE5T2R/xLVtfFI4cCePtiRn+e6jKMtFDR3P8V3qpv8wpKjwvHoBA4eJzvX+nVrnlNy0911bdGmuspCSwetfYHw==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-math": "^2.0.0", + "micromark-extension-math": "^2.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-2.3.0.tgz", + "integrity": "sha512-g53hMkpM0I98MU266IzDFMrTD980gNF3BJnkyFcmN+dD873mQeD5rdMO3Y2X+x8umQfbSE0PcoEDl7ledSA+2g==", + "dependencies": { + "mdast-util-mdx": "^2.0.0", + "micromark-extension-mdxjs": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz", + "integrity": "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-reading-time": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/remark-reading-time/-/remark-reading-time-2.0.1.tgz", + "integrity": "sha512-fy4BKy9SRhtYbEHvp6AItbRTnrhiDGbqLQTSYVbQPGuRCncU1ubSsh9p/W5QZSxtYcUXv8KGL0xBgPLyNJA1xw==", + "dependencies": { + "estree-util-is-identifier-name": "^2.0.0", + "estree-util-value-to-estree": "^1.3.0", + "reading-time": "^1.3.0", + "unist-util-visit": "^3.1.0" + } + }, + "node_modules/remark-reading-time/node_modules/unist-util-visit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-3.1.0.tgz", + "integrity": "sha512-Szoh+R/Ll68QWAyQyZZpQzZQm2UPbxibDvaY8Xc9SUtYgPsDzx5AWSk++UUt2hJuow8mvwR+rG+LQLw+KsuAKA==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-reading-time/node_modules/unist-util-visit-parents": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-4.1.1.tgz", + "integrity": "sha512-1xAFJXAKpnnJl8G7K5KgU7FY55y3GcLIXqkzUj5QF/QVP7biUm0K0O2oqVkYsdjzJKifYeWn9+o6piAK2hGSHw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz", + "integrity": "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-to-hast": "^12.1.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==" + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.0.10.tgz", + "integrity": "sha512-t44QCeDKAPf1mtQH3fYpWz8IM/DyvHLjs8wUvvwMYxk5moOqCzrMSxK6HQVD0QVmVjXFavoFIPRVrMuJPKAvtg==", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/search-insights": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.8.2.tgz", + "integrity": "sha512-PxA9M5Q2bpBelVvJ3oDZR8nuY00Z6qwOxL53wNpgzV28M/D6u9WUbImDckjLSILBF8F1hn/mgyuUaOPtjow4Qw==", + "peer": true + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/sharp": { + "version": "0.32.6", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", + "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", + "hasInstallScript": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.2", + "node-addon-api": "^6.1.0", + "prebuild-install": "^7.1.1", + "semver": "^7.5.4", + "simple-get": "^4.0.1", + "tar-fs": "^3.0.4", + "tunnel-agent": "^0.6.0" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shiki": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.3.tgz", + "integrity": "sha512-U3S/a+b0KS+UkTyMjoNojvTgrBHjgp7L6ovhFVZsXmBGnVdQ4K4U9oK0z63w538S91ATngv1vXigHCSWOwnr+g==", + "dependencies": { + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, + "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==" + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/sort-keys": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-5.0.0.tgz", + "integrity": "sha512-Pdz01AvCAottHTPQGzndktFNdbRA75BgOfeT1hH+AMnJFv8lynkPi42rfeEhpx1saTEI3YNMWxfqu0sFD1G8pw==", + "dependencies": { + "is-plain-obj": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sort-keys/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/streamx": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.1.tgz", + "integrity": "sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==", + "dependencies": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", + "integrity": "sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, + "node_modules/style-to-object": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.1.tgz", + "integrity": "sha512-HFpbb5gr2ypci7Qw+IOhnP2zOU7e77b+rzM+wTzXzfi1PrtBCX0E7Pk4wL4iTLnhzZ+JgEGAhX81ebTg/aYjQw==", + "dependencies": { + "inline-style-parser": "0.1.1" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true } } }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } + "node_modules/stylis": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz", + "integrity": "sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==" }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "node_modules/sucrase": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", + "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==", "dev": true, - "requires": { - "to-regex-range": "^5.0.1" + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "7.1.6", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=8" } }, - "fraction.js": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.2.tgz", - "integrity": "sha512-o2RiJQ6DZaR/5+Si0qJUIy637QMRudSi9kU/FFzx9EZazrIdnBgpU+3sEWCxAVhH2RtxW2Oz+T4p2o8uOPVcgA==", + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "dev": true, - "peer": true + "engines": { + "node": ">= 6" + } }, - "fs-extra": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", - "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "node_modules/supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha512-ycQR/UbvI9xIlEdQT1TQqwoXtEldExbCEAJgRo5YXlmSKjv6ThHnP9/vwGa1gr19Gfw+LkFd7KqYMhzrRC5JYw==", + "dependencies": { + "has-flag": "^2.0.0" + }, + "engines": { + "node": ">=4" } }, - "fs.realpath": { + "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "node_modules/sync-directory": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/sync-directory/-/sync-directory-6.0.4.tgz", + "integrity": "sha512-q+icVGkhOBu7i1M5J/bbs0kh2d64UpnVyMOKqw1bBqpYtr1zIPbW3ZcOZiG3U/dzPl1dCmEm3T6J8nvuq9dYsw==", "dev": true, - "optional": true + "dependencies": { + "chokidar": "^3.3.1", + "commander": "^6.2.0", + "fs-extra": "^7.0.1", + "is-absolute": "^1.0.0", + "readdir-enhanced": "^1.5.2" + }, + "bin": { + "syncdir": "cmd.js" + } }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "node_modules/sync-directory/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "engines": { + "node": ">= 6" + } }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "node_modules/tailwindcss": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz", + "integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==", "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.12", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.18.2", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" } }, - "glob-parent": { + "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==", "dev": true, - "requires": { + "dependencies": { "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" } }, - "graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "node_modules/tailwindcss/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==", "dev": true, - "requires": { - "function-bind": "^1.1.1" + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" } }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "hex-color-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", - "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", - "dev": true - }, - "hsl-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", - "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=", - "dev": true - }, - "hsla-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", - "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=", - "dev": true + "node_modules/tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "dependencies": { + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } }, - "html-tags": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", - "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==", - "dev": true + "node_modules/tar-stream": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", + "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "dependencies": { + "any-promise": "^1.0.0" } }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" } }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "node_modules/title": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/title/-/title-3.5.3.tgz", + "integrity": "sha512-20JyowYglSEeCvZv3EZ0nZ046vLarO37prvV0mbtQV7C8DJPGgN967r8SJkqd3XK3K3lD3/Iyfp3avjfil8Q2Q==", + "dependencies": { + "arg": "1.0.0", + "chalk": "2.3.0", + "clipboardy": "1.2.2", + "titleize": "1.0.0" + }, + "bin": { + "title": "bin/title.js" + } }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "node_modules/title/node_modules/arg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-1.0.0.tgz", + "integrity": "sha512-Wk7TEzl1KqvTGs/uyhmHO/3XLd3t1UeU4IstvPXVzGPM522cTjqjNZ99esCkcL52sjqjo8e8CTBcWhkxvGzoAw==" }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" + "node_modules/titleize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/titleize/-/titleize-1.0.0.tgz", + "integrity": "sha512-TARUb7z1pGvlLxgPk++7wJ6aycXF3GJ0sNSBTAsTuJrQG5QuZlkUQP+zl+nbjAh4gMX9yDw9ZYklMd7vAfJKEw==", + "engines": { + "node": ">=0.10.0" } }, - "is-color-stop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", - "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", - "dev": true, - "requires": { - "css-color-names": "^0.0.4", - "hex-color-regex": "^1.1.0", - "hsl-regex": "^1.0.0", - "hsla-regex": "^1.0.0", - "rgb-regex": "^1.0.1", - "rgba-regex": "^1.0.0" - } - }, - "is-core-module": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", - "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", - "dev": true, - "requires": { - "has": "^1.0.3" + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" } }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" + "node_modules/trough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", + "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "engines": { + "node": ">=6.10" + } }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "dev": true }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" } }, - "lilconfig": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", - "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==", - "dev": true + "node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "node_modules/unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "lodash.topath": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.topath/-/lodash.topath-4.5.2.tgz", - "integrity": "sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak=", - "dev": true + "node_modules/unified/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true + "node_modules/unist-util-find-after": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-4.0.1.tgz", + "integrity": "sha512-QO/PuPMm2ERxC6vFXEPtmAutOopy5PknD+Oq64gGwxKtk4xwo9Z97t9Av1obPmGU0IyTa6EKYUfTrK2QJS3Ozw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "node_modules/unist-util-generated": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", + "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" + "node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "node_modules/unist-util-position": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "modern-normalize": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/modern-normalize/-/modern-normalize-1.1.0.tgz", - "integrity": "sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA==", - "dev": true + "node_modules/unist-util-position-from-estree": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-1.1.2.tgz", + "integrity": "sha512-poZa0eXpS+/XpoQwGwl79UUdea4ol2ZuCYguVaJS4qzIOMDzbqz8a3erUCOmubSZkaOuGamb3tX790iwOIROww==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "nanoid": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", - "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", - "dev": true + "node_modules/unist-util-remove": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-4.0.0.tgz", + "integrity": "sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node-emoji": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", - "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", - "dev": true, - "requires": { - "lodash": "^4.17.21" + "node_modules/unist-util-remove-position": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-4.0.2.tgz", + "integrity": "sha512-TkBb0HABNmxzAcfLf4qsIbFbaPDvMO6wa3b3j4VcEzFVaw1LBKwnW4/sRJ/atSLSzoIg41JWEdnE7N6DIhGDGQ==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", - "dev": true, - "peer": true + "node_modules/unist-util-remove-position/node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "normalize-path": { + "node_modules/unist-util-remove/node_modules/@types/unist": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", - "dev": true, - "peer": true - }, - "object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", - "dev": true + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz", + "integrity": "sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==" }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" + "node_modules/unist-util-remove/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" + "node_modules/unist-util-remove/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" + "node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true + "node_modules/unist-util-visit/node_modules/@types/unist": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz", + "integrity": "sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==" }, - "postcss": { - "version": "8.4.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz", - "integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==", - "dev": true, - "requires": { - "nanoid": "^3.2.0", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "node_modules/unist-util-visit/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "postcss-js": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-3.0.3.tgz", - "integrity": "sha512-gWnoWQXKFw65Hk/mi2+WTQTHdPD5UJdDXZmX073EY/B3BWnYjO4F4t0VneTCnCGQ5E5GsCdMkzPaTXwl3r5dJw==", - "dev": true, - "requires": { - "camelcase-css": "^2.0.1", - "postcss": "^8.1.6" + "node_modules/unist-util-visit/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "postcss-load-config": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.1.tgz", - "integrity": "sha512-c/9XYboIbSEUZpiD1UQD0IKiUe8n9WHYV7YFe7X7J+ZwCsEKkUJSFWjS9hBU1RR9THR7jMXst8sxiqP0jjo2mg==", - "dev": true, - "requires": { - "lilconfig": "^2.0.4", - "yaml": "^1.10.2" - } + "node_modules/universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" }, - "postcss-nested": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz", - "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==", + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.6" + "engines": { + "node": ">= 4.0.0" } }, - "postcss-selector-parser": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz", - "integrity": "sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ==", + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", "dev": true, - "requires": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", - "dev": true + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, - "purgecss": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-4.1.3.tgz", - "integrity": "sha512-99cKy4s+VZoXnPxaoM23e5ABcP851nC2y2GROkkjS8eJaJtlciGavd7iYAw2V84WeBqggZ12l8ef44G99HmTaw==", - "dev": true, - "requires": { - "commander": "^8.0.0", - "glob": "^7.1.7", - "postcss": "^8.3.5", - "postcss-selector-parser": "^6.0.6" + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" } }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } }, - "quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true + "node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" + "node_modules/vfile-location": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz", + "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==", + "dependencies": { + "@types/unist": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "reduce-css-calc": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz", - "integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==", - "dev": true, - "requires": { - "css-unit-converter": "^1.1.1", - "postcss-value-parser": "^3.3.0" + "node_modules/vfile-matter": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vfile-matter/-/vfile-matter-3.0.1.tgz", + "integrity": "sha512-CAAIDwnh6ZdtrqAuxdElUqQRQDQgbbIrYtDYI8gCjXS1qQ+1XdLoK8FIZWxJwn0/I+BkSSZpar3SOgjemQz4fg==", + "dependencies": { + "@types/js-yaml": "^4.0.0", + "is-buffer": "^2.0.0", + "js-yaml": "^4.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-matter/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/vfile-matter/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - } + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", - "dev": true, - "requires": { - "is-core-module": "^2.8.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" + "node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true + "node_modules/vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==" }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true + "node_modules/vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==" }, - "rgb-regex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", - "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=", - "dev": true + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } }, - "rgba-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", - "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=", - "dev": true + "node_modules/watchpack/node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "run-parallel": { + "node_modules/web-worker": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz", + "integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==" }, - "simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "dev": true, - "requires": { - "is-arrayish": "^0.3.1" - }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dependencies": { - "is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "dev": true - } + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" } }, - "source-map-js": { + "node_modules/wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true + "node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" }, - "tailwindcss": { - "version": "2.2.19", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-2.2.19.tgz", - "integrity": "sha512-6Ui7JSVtXadtTUo2NtkBBacobzWiQYVjYW0ZnKaP9S1ZCKQ0w7KVNz+YSDI/j7O7KCMHbOkz94ZMQhbT9pOqjw==", - "dev": true, - "requires": { - "arg": "^5.0.1", - "bytes": "^3.0.0", - "chalk": "^4.1.2", - "chokidar": "^3.5.2", - "color": "^4.0.1", - "cosmiconfig": "^7.0.1", - "detective": "^5.2.0", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.2.7", - "fs-extra": "^10.0.0", - "glob-parent": "^6.0.1", - "html-tags": "^3.1.0", - "is-color-stop": "^1.1.0", - "is-glob": "^4.0.1", - "lodash": "^4.17.21", - "lodash.topath": "^4.5.2", - "modern-normalize": "^1.1.0", - "node-emoji": "^1.11.0", - "normalize-path": "^3.0.0", - "object-hash": "^2.2.0", - "postcss-js": "^3.0.3", - "postcss-load-config": "^3.1.0", - "postcss-nested": "5.0.6", - "postcss-selector-parser": "^6.0.6", - "postcss-value-parser": "^4.1.0", - "pretty-hrtime": "^1.0.3", - "purgecss": "^4.0.3", - "quick-lru": "^5.1.1", - "reduce-css-calc": "^2.1.8", - "resolve": "^1.20.0", - "tmp": "^0.2.1" - } - }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "node_modules/yaml": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", "dev": true, - "requires": { - "rimraf": "^3.0.0" + "engines": { + "node": ">= 14" } }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true + "node_modules/zod": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", + "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/website/package.json b/website/package.json index 538d960cd..efe85d270 100644 --- a/website/package.json +++ b/website/package.json @@ -1,5 +1,32 @@ { + "scripts": { + "dev": "next dev -p 8000", + "build": "next build", + "postbuild": "next-sitemap", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@aws-sdk/client-cloudwatch": "^3.393.0", + "@docsearch/css": "^3.5.2", + "@docsearch/react": "^3.5.2", + "@heroicons/react": "^2.0.18", + "@octokit/graphql": "^7.0.1", + "next": "^13.4.9", + "next-plausible": "^3.11.1", + "next-seo": "^6.1.0", + "next-sitemap": "^4.2.3", + "nextra": "^2.8.0", + "nextra-theme-docs": "^2.8.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sharp": "^0.32.6" + }, "devDependencies": { - "tailwindcss": "^2.0" + "@tailwindcss/forms": "^0.5.5", + "autoprefixer": "^10.4.14", + "postcss": "^8.4.25", + "sync-directory": "^6.0.4", + "tailwindcss": "^3.3.2" } } diff --git a/website/postcss.config.js b/website/postcss.config.js new file mode 100644 index 000000000..33ad091d2 --- /dev/null +++ b/website/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/website/public/apple-touch-icon-144x144.png b/website/public/apple-touch-icon-144x144.png new file mode 100644 index 000000000..d32e4e5ad Binary files /dev/null and b/website/public/apple-touch-icon-144x144.png differ diff --git a/website/public/apple-touch-icon-152x152.png b/website/public/apple-touch-icon-152x152.png new file mode 100644 index 000000000..bf8c20985 Binary files /dev/null and b/website/public/apple-touch-icon-152x152.png differ diff --git a/website/public/favicon-16x16.png b/website/public/favicon-16x16.png new file mode 100644 index 000000000..eba23de23 Binary files /dev/null and b/website/public/favicon-16x16.png differ diff --git a/website/public/favicon-32x32.png b/website/public/favicon-32x32.png new file mode 100644 index 000000000..e1979ec15 Binary files /dev/null and b/website/public/favicon-32x32.png differ diff --git a/website/public/favicon.ico b/website/public/favicon.ico new file mode 100644 index 000000000..eaf810690 Binary files /dev/null and b/website/public/favicon.ico differ diff --git a/docs/news/02/social-card.png b/website/public/news/02-social-card.png similarity index 100% rename from docs/news/02/social-card.png rename to website/public/news/02-social-card.png diff --git a/website/public/social-card.png b/website/public/social-card.png new file mode 100644 index 000000000..d0b783565 Binary files /dev/null and b/website/public/social-card.png differ diff --git a/website/redirects.js b/website/redirects.js new file mode 100644 index 000000000..56c8e18b7 --- /dev/null +++ b/website/redirects.js @@ -0,0 +1,69 @@ +module.exports.redirects = { + '/slack': 'https://join.slack.com/t/brefworkspace/shared_invite/enQtNTcwMjU2NTcxNjAxLTIxYmM2MmRjMDkzYjdjYTNkMmE5NGI3YTcyZjc2ZGRjNTFmNjFmYzk5NWQ1YmVhMDkwNzExNzhjZThkZWM0ODE', + '/#ecosystem': '/support', + '/#plans': '/support', + '/#enterprise': '/support', + '/docs/news': '/news', + '/docs/news/01-bref-1.0': '/news/01-bref-1.0', + '/docs/news/02-bref-2.0': '/news/02-bref-2.0', + '/docs/installation': '/docs/setup', + '/docs/first-steps': '/docs/setup#whats-next', + '/docs/runtimes#bref-ping': '/docs/runtimes/runtimes-details#telemetry-ping', + '/docs/runtimes/http': '/docs/runtimes/fpm-runtime', + '/docs/runtimes/fpm-runtime#setup': '/docs/runtimes/fpm-runtime#usage', + '/docs/runtimes/fpm-runtime#runtime': '/docs/runtimes/fpm-runtime', + '/docs/runtimes/fpm-runtime#routing': '/docs/use-cases/http#how-it-works', + '/docs/runtimes/fpm-runtime#binary-requests-and-responses': '/docs/use-cases/http/binary-requests-responses', + '/docs/runtimes/fpm-runtime#cold-starts': '/docs/use-cases/http/advanced-use-cases#cold-starts', + '/docs/function/handlers': '/docs/runtimes/function', + '/docs/runtimes/function#deployment-configuration': '/docs/runtimes/function#usage', + '/docs/runtimes/function#autoloading': '/docs/runtimes/function', + '/docs/runtimes/function#s3-events': '/docs/use-cases/s3', + '/docs/runtimes/function#sqs-events': '/docs/use-cases/sqs', + '/docs/runtimes/function#partial-batch-response': '/docs/use-cases/sqs#partial-batch-response', + '/docs/runtimes/function#lift-queue-construct': '/docs/use-cases/sqs#lift-queue-construct', + '/docs/runtimes/function#api-gateway-http-events': '/docs/use-cases/http/advanced-use-cases#php-handlers', + '/docs/runtimes/function#lambda-event-and-context': '/docs/use-cases/http/advanced-use-cases#lambda-event-and-context', + '/docs/runtimes/function#websocket-events': '/docs/use-cases/websockets', + '/docs/runtimes/function#eventbridge-events': '/docs/use-cases/eventbridge', + '/docs/runtimes/function#sns-events': '/docs/use-cases/sns', + '/docs/runtimes/function#dynamodb-events': '/docs/use-cases/dynamodb', + '/docs/runtimes/function#kinesis-events': '/docs/use-cases/kinesis', + '/docs/runtimes/function#kafka-events': '/docs/use-cases/kafka', + '/docs/websites': '/docs/use-cases/websites', + '/docs/use-cases/websites#setting-up-a-domain-name': '/docs/use-cases/websites#custom-domain-name', + '/docs/web-apps/docker': '/docs/deploy/docker', + '/docs/installation/aws-keys': '/docs/setup/aws-keys', + '/docs/environment/custom-domains': '/docs/use-cases/http/custom-domains', + '/docs/aws-cdk': '/docs/deploy/aws-cdk', + '/docs/web-apps/index#assets': '/docs/use-cases/websites', + '/docs/function/local-development': '/docs/local-development/event-driven-functions', + '/docs/frameworks/laravel': '/docs/laravel/getting-started', + '/docs/laravel/getting-started#assets': '/docs/laravel/getting-started#website-assets', + '/docs/laravel/getting-started#file-storage-on-s3': '/docs/laravel/file-storage', + '/docs/laravel/getting-started#public-files': '/docs/laravel/file-storage#public-files', + '/docs/laravel/getting-started#laravel-queues': '/docs/laravel/queues', + '/docs/laravel/getting-started#how-it-works-1': '/docs/laravel/queues#how-it-works', + '/docs/laravel/getting-started#laravel-octane': '/docs/laravel/octane', + '/docs/laravel/getting-started#persistent-database-connections': '/docs/laravel/octane#persistent-database-connections', + '/docs/laravel/getting-started#caching': '/docs/laravel/caching', + '/docs/laravel/getting-started#maintenance-mode': '/docs/laravel/maintenance-mode', + '/docs/laravel/getting-started#laravel-passport': '/docs/laravel/passport', + '/docs/frameworks/symfony': '/docs/symfony/getting-started', + '/docs/symfony/getting-started#deploy': '/docs/symfony/getting-started#deployment', + '/docs/symfony/getting-started#console': '/docs/symfony/getting-started#symfony-console', + '/docs/symfony/getting-started#assets': '/docs/symfony/getting-started#website-assets', + '/docs/symfony/getting-started#symfony-messenger': '/docs/symfony/messenger', + '/docs/symfony/getting-started#caching': '/docs/symfony/caching', + '/docs/newsletters': '/docs/community#newsletters', + '/docs/newsletters.html': '/docs/community#newsletters', + '/docs/cron': '/docs/use-cases/cron', + '/docs/cron.html': '/docs/use-cases/cron', + '/docs/web-apps/cron': '/docs/use-cases/cron', + '/docs/web-apps/cron.html': '/docs/use-cases/cron', + '/docs/web-apps/local-development': '/docs/local-development', + '/docs/web-apps/local-development.html': '/docs/local-development', + '/docs/runtimes/console#configuration': '/docs/runtimes/console#usage', + '/docs/runtimes/console#usage-without-serverless-framework': '/docs/runtimes/console#without-serverless-framework', + '/docs/function/cron': '/docs/use-cases/cron#cron-functions', +}; diff --git a/website/src/aws/invocations.js b/website/src/aws/invocations.js new file mode 100644 index 000000000..754c2066a --- /dev/null +++ b/website/src/aws/invocations.js @@ -0,0 +1,37 @@ +import { CloudWatchClient, GetMetricDataCommand } from '@aws-sdk/client-cloudwatch'; + +/** + * @returns {Promise} + */ +export async function getBrefInvocations() { + const cloudWatch = new CloudWatchClient({ + region: 'us-east-1' + }); + + const response = await cloudWatch.send(new GetMetricDataCommand({ + // 30 days ago + StartTime: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30), + // Now + EndTime: new Date(), + MetricDataQueries: [ + { + Id: 'invocations', + MetricStat: { + Metric: { + Namespace: 'Bref/Stats', + MetricName: 'Invocations_100' + }, + Period: 60 * 60 * 24 * 30, // the whole month + Stat: 'Sum', + Unit: 'Count' + } + } + ] + })); + // If we are between 2 months, it will return 1 value per month so we must deal with that + const invocationMetrics = response.MetricDataResults[0].Values ? response.MetricDataResults[0].Values : []; + let invocations = invocationMetrics.reduce((a, b) => a + b, 0); + // We must multiply by 100 because the metric is collected every 100 invocations + invocations *= 100; + return invocations; +} diff --git a/website/src/components/AnimatedLogo.jsx b/website/src/components/AnimatedLogo.jsx new file mode 100644 index 000000000..30f60a1aa --- /dev/null +++ b/website/src/components/AnimatedLogo.jsx @@ -0,0 +1,21 @@ +import { useEffect, useState } from 'react'; + +export function AnimatedLogo({ children, ...props }) { + const [counter, setCounter] = useState(0); + useEffect(() => { + const interval = setInterval(() => { + setCounter((counter + 1) % 28); + }, 200); + return () => clearInterval(interval); + }, [counter]); + + return + + + + + + + + ; +} diff --git a/website/src/components/Breadcrumbs.jsx b/website/src/components/Breadcrumbs.jsx new file mode 100644 index 000000000..d134c5f34 --- /dev/null +++ b/website/src/components/Breadcrumbs.jsx @@ -0,0 +1,31 @@ +import { ChevronRightIcon } from '@heroicons/react/20/solid' + +/** + * @param {Array<{name: string; href: string; current: boolean}>} pages + */ +export default function Breadcrumbs({ pages }) { + return ( + + ) +} diff --git a/website/src/components/Footer.jsx b/website/src/components/Footer.jsx new file mode 100644 index 000000000..e68721b23 --- /dev/null +++ b/website/src/components/Footer.jsx @@ -0,0 +1,63 @@ +import { TwitterIcon } from './icons/TwitterIcon'; +import { GitHubIcon } from './icons/GitHubIcon'; +import { SlackIcon } from './icons/SlackIcon'; + +const navigation = { + main: [ + { name: 'Home', href: '/' }, + { name: 'Documentation', href: '/docs/' }, + { name: 'News', href: '/news/' }, + { name: 'Support', href: '/support' }, + { name: 'Bref Dashboard', href: 'https://dashboard.bref.sh/' }, + { name: 'Credits', href: '/credits' }, + ], + social: [ + { + name: 'GitHub', + href: 'https://github.com/brefphp/bref', + icon: GitHubIcon, + }, + { + name: 'Twitter', + href: 'https://twitter.com/brefphp', + icon: TwitterIcon, + }, + { + name: 'Slack', + href: '/slack', + icon: SlackIcon, + }, + ], +} + +export default function Footer() { + return ( + + ) +} diff --git a/website/src/components/Logo.jsx b/website/src/components/Logo.jsx new file mode 100644 index 000000000..01cba69b3 --- /dev/null +++ b/website/src/components/Logo.jsx @@ -0,0 +1,6 @@ +export function Logo({ children, ...props }) { + return + + + ; +} diff --git a/website/src/components/home/companies.jsx b/website/src/components/home/companies.jsx new file mode 100644 index 000000000..35371c585 --- /dev/null +++ b/website/src/components/home/companies.jsx @@ -0,0 +1,78 @@ +import Image from 'next/image'; +import phpStanLogo from './companies/phpstan.svg'; +import bcastLogo from './companies/bcast.svg'; +import myBuilderLogo from './companies/mybuilder.svg'; +import neuralLoveLogo from './companies/neural-love.svg'; +import enopteaLogo from './companies/enoptea.png'; +import gulliLogo from './companies/gulli.svg'; +import minutesLogo from './companies/20minutes.svg'; + +export default function Companies() { + return ( +
+
+
+
+

+ Used by thousands of companies +

+

+ Get started with Bref on your own, or get in touch for support and consulting. +

+ +
+
+ PhpStan + 20minutes.fr + MyBuilder + Gulli.fr + Enoptea + bCast.fm +
+ externals.io +
+ neural.love +
+
+
+
+ ) +} diff --git a/website/src/components/home/companies/20minutes.svg b/website/src/components/home/companies/20minutes.svg new file mode 100644 index 000000000..6d825e44e --- /dev/null +++ b/website/src/components/home/companies/20minutes.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/website/src/components/home/companies/bcast.svg b/website/src/components/home/companies/bcast.svg new file mode 100644 index 000000000..a58487e56 --- /dev/null +++ b/website/src/components/home/companies/bcast.svg @@ -0,0 +1 @@ +bcast \ No newline at end of file diff --git a/website/src/components/home/companies/enoptea.png b/website/src/components/home/companies/enoptea.png new file mode 100644 index 000000000..68a6501a6 Binary files /dev/null and b/website/src/components/home/companies/enoptea.png differ diff --git a/website/src/components/home/companies/gulli.svg b/website/src/components/home/companies/gulli.svg new file mode 100644 index 000000000..b9615c66d --- /dev/null +++ b/website/src/components/home/companies/gulli.svg @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/website/src/components/home/companies/mybuilder.svg b/website/src/components/home/companies/mybuilder.svg new file mode 100644 index 000000000..0ac98d60d --- /dev/null +++ b/website/src/components/home/companies/mybuilder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/src/components/home/companies/neural-love.svg b/website/src/components/home/companies/neural-love.svg new file mode 100644 index 000000000..1222f3f3f --- /dev/null +++ b/website/src/components/home/companies/neural-love.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/website/src/components/home/companies/phpstan.svg b/website/src/components/home/companies/phpstan.svg new file mode 100644 index 000000000..6fbaf5c74 --- /dev/null +++ b/website/src/components/home/companies/phpstan.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/website/src/components/home/intro.jsx b/website/src/components/home/intro.jsx new file mode 100644 index 000000000..2211960d7 --- /dev/null +++ b/website/src/components/home/intro.jsx @@ -0,0 +1,92 @@ +import { CloudArrowUpIcon, PresentationChartLineIcon, BanknotesIcon } from '@heroicons/react/20/solid' + +const features = [ + { + name: 'Simple.', + description: + 'Instead of setting up and maintaining servers, define your application in a simple serverless.yml file. Then deploy to AWS with serverless deploy. Bref integrates with the Serverless Framework for a great developer experience.', + icon: CloudArrowUpIcon, + }, + { + name: 'Scalable.', + description: 'Bref provides open-source runtimes to run PHP on AWS Lambda. AWS Lambda runs your code redundantly across data centers and scales in real-time. All automatically. Handle 1 request/second or 1000 with the same code.', + icon: PresentationChartLineIcon, + }, + { + name: 'Cost-efficient.', + description: 'Instead of paying for servers that are idle most of the time, pay for the time the code is actually running. The AWS free tier even provides about 1 million free requests per month. Play with the serverless costs calculator.', + icon: BanknotesIcon, + }, +] + +export default function Intro() { + const date = new Date(); + const year = date.getFullYear(); + + return ( +
+
+
+
+

+ Why Bref? Why serverless? +

+

+ We're in {year}. + Applications should scale automatically. + Hosting should be reliable and cost-efficient. + Infrastructure should accelerate development, not consume our time. +

+

+ Bref deploys PHP applications to {' '} + AWS Lambda {' '} + and sets up the rest of the infrastructure using serverless services. +

+
+ {features.map((feature) => ( +
+
+
{' '} +
+
+ ))} +
+
+
+
+
+ +
+
+ ) +} diff --git a/website/src/components/home/invocations.jsx b/website/src/components/home/invocations.jsx new file mode 100644 index 000000000..e5388fc20 --- /dev/null +++ b/website/src/components/home/invocations.jsx @@ -0,0 +1,55 @@ +export default function Invocations({ invocations }) { + return ( +
+
+

+ {invocations?.toLocaleString('en-US')} +

+

+ requests, jobs, and messages handled with Bref in the last 30 days +

+ {/**/} +
+
    +
    + + +
    +
    + +
    +
    + ); +} diff --git a/website/src/components/home/sponsors.jsx b/website/src/components/home/sponsors.jsx new file mode 100644 index 000000000..4ce56a581 --- /dev/null +++ b/website/src/components/home/sponsors.jsx @@ -0,0 +1,50 @@ +import GoldSponsor from './sponsors/gold-sponsor'; +import craftLogo from './sponsors/logo-craft-cms.png'; +import tidewaysLogo from './sponsors/logo-tideways.svg'; +import myBuilderLogo from './sponsors/logo-mybuilder.svg'; +import shippyProLogo from './sponsors/logo-shippypro.png'; +import nullLogo from './sponsors/logo-null.png'; +import awsLogo from './sponsors/logo-aws.svg'; +import jetbrainsLogo from './sponsors/logo-jetbrains.svg'; +import laravelLogo from './sponsors/logo-laravel.svg'; +import depotLogo from './sponsors/logo-depot.svg'; +import secumailerLogo from './sponsors/logo-secumailer.svg'; +import ecomailLogo from './sponsors/logo-ecomail.png'; +import PremiumSponsor from './sponsors/premium-sponsor'; + +export default function Sponsors() { + return ( +
    +

    + They sponsor the open-source project ❤️ +

    + +

    + Premium sponsors +

    +
    + + + + + + +
    + +

    + Gold sponsors +

    +
    + + + + + +
    + +

    + Become a sponsor and help Bref be a sustainable open-source project. +

    +
    + ); +} diff --git a/website/src/components/home/sponsors/gold-sponsor.jsx b/website/src/components/home/sponsors/gold-sponsor.jsx new file mode 100644 index 000000000..fc0bb8098 --- /dev/null +++ b/website/src/components/home/sponsors/gold-sponsor.jsx @@ -0,0 +1,14 @@ +import Image from 'next/image'; + +export default function GoldSponsor({ src, alt, href, imgClass }) { + return ( + + {alt} + + ); +} diff --git a/website/template/img/aws.svg b/website/src/components/home/sponsors/logo-aws.svg similarity index 100% rename from website/template/img/aws.svg rename to website/src/components/home/sponsors/logo-aws.svg diff --git a/website/template/img/logo-craft-cms.png b/website/src/components/home/sponsors/logo-craft-cms.png similarity index 100% rename from website/template/img/logo-craft-cms.png rename to website/src/components/home/sponsors/logo-craft-cms.png diff --git a/website/template/img/logo-depot.svg b/website/src/components/home/sponsors/logo-depot.svg similarity index 100% rename from website/template/img/logo-depot.svg rename to website/src/components/home/sponsors/logo-depot.svg diff --git a/website/template/img/logo-ecomail.png b/website/src/components/home/sponsors/logo-ecomail.png similarity index 100% rename from website/template/img/logo-ecomail.png rename to website/src/components/home/sponsors/logo-ecomail.png diff --git a/website/template/img/logo-jetbrains.svg b/website/src/components/home/sponsors/logo-jetbrains.svg similarity index 100% rename from website/template/img/logo-jetbrains.svg rename to website/src/components/home/sponsors/logo-jetbrains.svg diff --git a/website/template/img/logo-laravel.svg b/website/src/components/home/sponsors/logo-laravel.svg similarity index 100% rename from website/template/img/logo-laravel.svg rename to website/src/components/home/sponsors/logo-laravel.svg diff --git a/website/template/img/logo-mybuilder.svg b/website/src/components/home/sponsors/logo-mybuilder.svg similarity index 100% rename from website/template/img/logo-mybuilder.svg rename to website/src/components/home/sponsors/logo-mybuilder.svg diff --git a/website/template/img/logo-null.png b/website/src/components/home/sponsors/logo-null.png similarity index 100% rename from website/template/img/logo-null.png rename to website/src/components/home/sponsors/logo-null.png diff --git a/website/template/img/logo-secumailer.svg b/website/src/components/home/sponsors/logo-secumailer.svg similarity index 100% rename from website/template/img/logo-secumailer.svg rename to website/src/components/home/sponsors/logo-secumailer.svg diff --git a/website/template/img/logo-shippypro.png b/website/src/components/home/sponsors/logo-shippypro.png similarity index 100% rename from website/template/img/logo-shippypro.png rename to website/src/components/home/sponsors/logo-shippypro.png diff --git a/website/template/img/logo-tideways.svg b/website/src/components/home/sponsors/logo-tideways.svg similarity index 100% rename from website/template/img/logo-tideways.svg rename to website/src/components/home/sponsors/logo-tideways.svg diff --git a/website/src/components/home/sponsors/premium-sponsor.jsx b/website/src/components/home/sponsors/premium-sponsor.jsx new file mode 100644 index 000000000..8b65b04e0 --- /dev/null +++ b/website/src/components/home/sponsors/premium-sponsor.jsx @@ -0,0 +1,17 @@ +import Image from 'next/image'; + +export default function PremiumSponsor({ src, alt, href, oneTime }) { + return ( + + {alt} + { oneTime &&
    + * one-time sponsor +
    } +
    + ); +} diff --git a/website/src/components/home/testimonials.jsx b/website/src/components/home/testimonials.jsx new file mode 100644 index 000000000..7362413f2 --- /dev/null +++ b/website/src/components/home/testimonials.jsx @@ -0,0 +1,141 @@ +import Image from 'next/image'; +import neil from './testimonials/neil.jpg'; +import geeh from './testimonials/geeh.jpg'; +import paul from './testimonials/paul.jpg'; +import marco from './testimonials/marco.jpg'; +import robdwaller from './testimonials/robdwaller.jpg'; +import aranreeks from './testimonials/aranreeks.jpg'; +import nyholm from './testimonials/nyholm.jpg'; +import zmalter from './testimonials/zmalter.jpg'; +import simon from './testimonials/simon.jpg'; +import lorenzo from './testimonials/lorenzo.jpg'; + +const testimonials = [ + { + body: 'Bref is excellent. We\'ve been running a Laravel app with it since 2020 and it\'s currently handling over 160 million requests per month without a hiccup.', + author: { + name: 'Neil Morgan', + handle: 'neil-r-morgan', + link: 'https://www.linkedin.com/in/neil-r-morgan/', + image: neil, + }, + }, + { + body: 'Bref has been a boon for running our customer\'s applications. We\'ve had a Laravel API on Bref for the last 12 months serve over 25 million requests with an average response time of 50ms.', + author: { + name: 'Paul Giberson', + handle: 'HalasLabs', + link: 'https://twitter.com/HalasLabs/status/1638650910971932672', + image: paul, + }, + }, + { + body: 'Every time I throw something up onto AWS Lambda in PHP using Bref I marvel at how mega-useful it is. If you haven’t checked out Bref you’re probably missing out', + author: { + name: 'Gary Hockin', + handle: 'GeeH', + link: 'https://twitter.com/GeeH/status/1335909653897752576', + image: geeh, + }, + }, + { + body: 'Just finished migrating our production from Heroku to AWS Lambda via Bref. It\'ll save us around $2k a year 🤯', + author: { + name: 'Zach Malter', + handle: 'zmalter99', + link: 'https://twitter.com/zmalter99/status/1671228229317689367', + image: zmalter, + }, + }, + { + body: 'When your production website with Symfony, API Platform and Bref handles more than 500 simultaneous connections without flinching…', + author: { + name: '$!m0n', + handle: '__si_mon', + link: 'https://twitter.com/__si_mon/status/1616778693212348416', + image: simon, + }, + }, + { + body: 'I’ve been running APIs and websites with Bref in prod for over a year now. It is indeed as simple as you describe it.', + author: { + name: 'Tobias Nyholm', + handle: 'TobiasNyholm', + link: 'https://twitter.com/TobiasNyholm/status/1292027581986934785', + image: nyholm, + }, + }, + { + body: 'There is something amazing and magical about Bref and serverless deploying stuff to the cloud.', + author: { + name: 'Rob Waller', + handle: 'RobDWaller', + link: 'https://twitter.com/RobDWaller/status/1484569852694118406', + image: robdwaller, + }, + }, + { + body: 'Happily using Bref since 2019 to process millions of requests, jobs and scheduled tasks. It powers the best technical accomplishment of my career and has made me a better software engineer and open-source contributor.', + author: { + name: 'Marco Deleu', + handle: 'deleugyn', + link: 'https://twitter.com/deleugyn', + image: marco, + }, + }, + { + body: 'The team embraced Bref with lightning speed! We were able to roll out the first parts of our new event-driven architecture within days, crafting new features in a mere fraction of the time it used to take — truly mind-blowing!', + author: { + name: 'Lorenzo Rogai', + handle: 'lorenzo-rogai', + link: 'https://www.linkedin.com/in/lorenzo-rogai/', + image: lorenzo, + }, + }, + { + body: 'An incredible project and one we\'re very proud to use in production for a recent eCommerce project we launched that saw 32 million Lambda invocations last month.', + author: { + name: 'Aran Reeks', + handle: 'AranReeks', + link: 'https://twitter.com/AranReeks/status/1332467843254919168', + image: aranreeks, + }, + }, + // More testimonials... +] + +export default function Testimonials() { + return ( +
    +

    + Happy users and community +

    +
    +
    + {testimonials.map((testimonial) => ( +
    +
    +
    +

    +
    +
    + {testimonial.author.imageUrl ? ( + {testimonial.author.name} + ) : ( + {testimonial.author.name} + )} +
    +
    {testimonial.author.name}
    + {`@${testimonial.author.handle}`} +
    +
    +
    +
    + ))} +
    +
    +
    + ) +} diff --git a/website/src/components/home/testimonials/aranreeks.jpg b/website/src/components/home/testimonials/aranreeks.jpg new file mode 100644 index 000000000..b4d9910e3 Binary files /dev/null and b/website/src/components/home/testimonials/aranreeks.jpg differ diff --git a/website/src/components/home/testimonials/geeh.jpg b/website/src/components/home/testimonials/geeh.jpg new file mode 100644 index 000000000..6be1b99a4 Binary files /dev/null and b/website/src/components/home/testimonials/geeh.jpg differ diff --git a/website/src/components/home/testimonials/lorenzo.jpg b/website/src/components/home/testimonials/lorenzo.jpg new file mode 100644 index 000000000..8b9ac3426 Binary files /dev/null and b/website/src/components/home/testimonials/lorenzo.jpg differ diff --git a/website/src/components/home/testimonials/marco.jpg b/website/src/components/home/testimonials/marco.jpg new file mode 100644 index 000000000..e3dbe052a Binary files /dev/null and b/website/src/components/home/testimonials/marco.jpg differ diff --git a/website/src/components/home/testimonials/neil.jpg b/website/src/components/home/testimonials/neil.jpg new file mode 100644 index 000000000..62ef2c504 Binary files /dev/null and b/website/src/components/home/testimonials/neil.jpg differ diff --git a/website/src/components/home/testimonials/nyholm.jpg b/website/src/components/home/testimonials/nyholm.jpg new file mode 100644 index 000000000..35adcba00 Binary files /dev/null and b/website/src/components/home/testimonials/nyholm.jpg differ diff --git a/website/src/components/home/testimonials/paul.jpg b/website/src/components/home/testimonials/paul.jpg new file mode 100644 index 000000000..463a03a6e Binary files /dev/null and b/website/src/components/home/testimonials/paul.jpg differ diff --git a/website/src/components/home/testimonials/robdwaller.jpg b/website/src/components/home/testimonials/robdwaller.jpg new file mode 100644 index 000000000..6e0cd6857 Binary files /dev/null and b/website/src/components/home/testimonials/robdwaller.jpg differ diff --git a/website/src/components/home/testimonials/simon.jpg b/website/src/components/home/testimonials/simon.jpg new file mode 100644 index 000000000..2e3917373 Binary files /dev/null and b/website/src/components/home/testimonials/simon.jpg differ diff --git a/website/src/components/home/testimonials/zmalter.jpg b/website/src/components/home/testimonials/zmalter.jpg new file mode 100644 index 000000000..ed4cbf403 Binary files /dev/null and b/website/src/components/home/testimonials/zmalter.jpg differ diff --git a/website/src/components/home/use-cases.jsx b/website/src/components/home/use-cases.jsx new file mode 100644 index 000000000..fba03d934 --- /dev/null +++ b/website/src/components/home/use-cases.jsx @@ -0,0 +1,93 @@ +import { CheckIcon } from '@heroicons/react/20/solid'; + +const simpleUseCases = [ + { + name: 'Websites', + description: 'Run PHP websites with Laravel, Symfony or any other framework, with a worldwide CDN and your custom domain.' + }, + { + name: 'HTTP APIs', + description: 'REST or GraphQL APIs deployed in seconds. Need more performance? Enable Laravel Octane or the Symfony equivalent.' + }, + { + name: 'CLI commands', + description: 'Run DB migrations, admin commands, or any other CLI command from your machine or your CI/CD.' + }, + { + name: 'Cron tasks', + description: 'Every day, every hour, every minute… Run CLI scripts, Symfony Console commands, or the Laravel Scheduler.' + }, +]; + +const advancedUseCases = [ + { + name: 'Job queues', + description: 'Run 1000 jobs with 1 worker in 1000 seconds, or with 1000 workers in 1 second. It\'s just as simple and it costs the same. SQS invokes your code directly, no long-running process to maintain.' + }, + { + name: 'Event-driven microservices', + description: 'Decouple and scale microservices without container madness. Send messages to EventBridge and let it invoke your PHP classes directly. No integration to write.' + }, + { + name: 'File processing', + description: 'S3 can invoke a PHP class whenever a new file is uploaded. Resize images, convert videos, generate PDFs…' + }, + { + name: 'WebSockets', + description: 'AWS API Gateway manages the WebSocket connections for you. Send messages to your users in real-time.' + }, +]; + +export default function UseCases() { + return ( +
    +
    +
    +

    Use cases

    +

    + Serverless means whatever you choose it to mean. +

    +

    + Run PHP as usual, {' '}like on any server. + Except it scales (almost) infinitely and you don't maintain the infrastructure.
    + Lift-and-shift existing apps or build new ones with your favorite framework. +

    +
    +
    + {simpleUseCases.map((feature) => ( +
    +
    +
    +
    +
    + ))} +
    +
    +
    +
    +

    + Or go the extreme opposite: build {' '}event-driven microservices with infinitely scalable cloud services like SQS and EventBridge. +

    +

    + Or anything in between, that works too. +

    +
    +
    + {advancedUseCases.map((feature) => ( +
    +
    +
    +
    +
    + ))} +
    +
    +
    + ); +} diff --git a/website/src/components/icons/GitHubIcon.jsx b/website/src/components/icons/GitHubIcon.jsx new file mode 100644 index 000000000..a048e2648 --- /dev/null +++ b/website/src/components/icons/GitHubIcon.jsx @@ -0,0 +1,7 @@ +export function GitHubIcon({ children, ...props }) { + return + GitHub + + ; +} diff --git a/website/src/components/icons/LaravelFullIcon.jsx b/website/src/components/icons/LaravelFullIcon.jsx new file mode 100644 index 000000000..37e00a2cd --- /dev/null +++ b/website/src/components/icons/LaravelFullIcon.jsx @@ -0,0 +1,5 @@ +export function LaravelFullIcon({children, ...props}) { + return + + +} diff --git a/website/src/components/icons/LaravelIcon.jsx b/website/src/components/icons/LaravelIcon.jsx new file mode 100644 index 000000000..e3e82fa49 --- /dev/null +++ b/website/src/components/icons/LaravelIcon.jsx @@ -0,0 +1,6 @@ +export function LaravelIcon() { + return Laravel + + +} diff --git a/website/src/components/icons/SlackIcon.jsx b/website/src/components/icons/SlackIcon.jsx new file mode 100644 index 000000000..3d9acf3bc --- /dev/null +++ b/website/src/components/icons/SlackIcon.jsx @@ -0,0 +1,3 @@ +export function SlackIcon({children, ...props}) { + return +} diff --git a/website/src/components/icons/SymfonyFullIcon.jsx b/website/src/components/icons/SymfonyFullIcon.jsx new file mode 100644 index 000000000..aae6f4cbb --- /dev/null +++ b/website/src/components/icons/SymfonyFullIcon.jsx @@ -0,0 +1,10 @@ +export function SymfonyFullIcon({children, ...props}) { + return + Symfony + + + + +} diff --git a/website/src/components/icons/SymfonyIcon.jsx b/website/src/components/icons/SymfonyIcon.jsx new file mode 100644 index 000000000..0aee8d3ab --- /dev/null +++ b/website/src/components/icons/SymfonyIcon.jsx @@ -0,0 +1,8 @@ +export function SymfonyIcon() { + return + + + + + +} diff --git a/website/src/components/icons/TwitterIcon.jsx b/website/src/components/icons/TwitterIcon.jsx new file mode 100644 index 000000000..4e62c1808 --- /dev/null +++ b/website/src/components/icons/TwitterIcon.jsx @@ -0,0 +1,6 @@ +export function TwitterIcon({children, ...props}) { + return + + ; +} diff --git a/website/src/components/logo.svg b/website/src/components/logo.svg new file mode 100644 index 000000000..5f12c54e8 --- /dev/null +++ b/website/src/components/logo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/website/src/components/news/ArticleHeader.jsx b/website/src/components/news/ArticleHeader.jsx new file mode 100644 index 000000000..3d7000014 --- /dev/null +++ b/website/src/components/news/ArticleHeader.jsx @@ -0,0 +1,15 @@ +export default function ArticleHeader({ subTitle, date, author, authorGitHub, ...props }) { + return
    +
    + {subTitle} +
    +
    + {author} +
    +
    {date}
    + {author} +
    +
    +
    ; +} \ No newline at end of file diff --git a/website/src/components/sentry/sentry.png b/website/src/components/sentry/sentry.png new file mode 100644 index 000000000..5778a1b01 Binary files /dev/null and b/website/src/components/sentry/sentry.png differ diff --git a/website/src/components/support/plans.jsx b/website/src/components/support/plans.jsx new file mode 100644 index 000000000..1e18c73d2 --- /dev/null +++ b/website/src/components/support/plans.jsx @@ -0,0 +1,240 @@ +import { Fragment } from 'react' +import { CheckIcon, MinusIcon } from '@heroicons/react/20/solid' + +const tiers = [ + { + name: 'OpenSource', + id: 'open-source', + href: '/docs/', + buttonText: 'Get Started', + priceMonthly: 'Free and Open Source', + description: 'Bref', + mostPopular: false, + }, + { + name: 'Pro', + id: 'pro', + href: 'https://matthieunapoli.typeform.com/to/d2OoXKln', + buttonText: 'Get Started', + priceMonthly: 100, + description: 'Bref Pro', + mostPopular: true, + }, + { + name: 'Enterprise', + id: 'enterprise', + href: 'mailto:enterprise@bref.sh?subject=Bref%20Enterprise', + buttonText: 'Get in touch', + priceMonthly: 'Get in touch', + description: 'Bref Enterprise', + mostPopular: false, + }, +] +const sections = [ + { + name: 'Bref and AWS deployments', + features: [ + { name: 'The Bref open-source project, its documentation and framework integrations', tiers: { OpenSource: true, Pro: true, Enterprise: true } }, + { name: 'Deploy your applications to your AWS account', tiers: { OpenSource: true, Pro: true, Enterprise: true } }, + { name: ' Tailor-made AWS Lambda runtimes and PHP extensions optimized for your project', tiers: { Enterprise: 'Optional' } }, + { name: 'Appear as an open-source sponsor 💙', tiers: { Pro: 'Gold sponsor', Enterprise: 'Premium sponsor' } }, + ], + }, + { + name: 'Resources and support', + features: [ + { name: 'Priority support and bugfixes on GitHub', tiers: { Pro: true, Enterprise: true } }, + { name: 'Expert support via Slack and Email', tiers: { Pro: true, Enterprise: true } }, + { name: 'Architecture design and review in Zoom', tiers: { Enterprise: true } }, + { name: 'GitHub/GitLab infrastructure code review', tiers: { Enterprise: true } }, + { name: 'Unlimited access to the Serverless Visually Explained course', tiers: { Enterprise: true } }, + { name: 'Onboarding workshop online or on-site', tiers: { Enterprise: 'Optional' } }, + ], + }, +] + +function classNames(...classes) { + return classes.filter(Boolean).join(' ') +} + +export default function Plans() { + return ( +
    +
    +

    + Get more with Bref Pro and Enterprise +

    +
    +

    + Bref is a free and open-source project hosted on GitHub. +
    + Bref Pro and Bref Enterprise are support plans offered by Null, the company behind Bref. +

    + + {/* xs to lg */} +
    + {tiers.map((tier) => ( +
    +

    + {tier.description} +

    +

    + {typeof tier.priceMonthly === 'number' ? ({tier.priceMonthly}) : ({tier.priceMonthly})} + + {typeof tier.priceMonthly === 'number' && (€/month)} +

    + + {tier.buttonText} + +
      + {sections.map((section) => ( +
    • +
        + {section.features.map((feature) => + feature.tiers[tier.name] ? ( +
      • +
      • + ) : null + )} +
      +
    • + ))} +
    +
    + ))} +
    + + {/* lg+ */} +
    +
    + {tiers.some((tier) => tier.mostPopular) ? ( +
    + + ) : null} + + + + + + + + + + + + ))} + + + + + + {tiers.map((tier) => ( + + ))} + + {sections.map((section, sectionIdx) => ( + + + + + {section.features.map((feature) => ( + + + {tiers.map((tier) => ( + + ))} + + ))} + + ))} + +
    Pricing plan comparison
    + {tiers.map((tier) => ( + +
    {tier.description}
    +
    + Price + +
    + {typeof tier.priceMonthly === 'number' ? ({tier.priceMonthly}) : ({tier.priceMonthly})} + + {typeof tier.priceMonthly === 'number' && (€/month)} +
    + + {tier.buttonText} + +
    + {section.name} +
    +
    + +
    +
    + {typeof feature.tiers[tier.name] === 'string' ? ( +
    + {feature.tiers[tier.name]} +
    + ) : ( + <> + {feature.tiers[tier.name] === true ? ( +
    +
    +
    +
    + ) +} diff --git a/website/src/github/contributors.js b/website/src/github/contributors.js new file mode 100644 index 000000000..e1d17fd6d --- /dev/null +++ b/website/src/github/contributors.js @@ -0,0 +1,29 @@ +export async function getContributors(repository) { + // Retrieve the list of contributors from the GitHub REST API + // See https://docs.github.com/en/rest/reference/repos#list-repository-contributors + const contributors = []; + let page = 1; + let hasNextPage = true; + while (hasNextPage) { + const response = await fetch(`https://api.github.com/repos/${repository}/contributors?page=${page}&per_page=100`); + const data = await response.json(); + contributors.push(...data.map(({ login, html_url, contributions }) => ({ login, html_url, contributions }))); + hasNextPage = response.headers.get('Link')?.includes('rel="next"'); + page++; + } + return contributors; +} + +export function mergeAndSortContributors(contributors) { + // Merge duplicates and sum contributions + const contributorsMap = {}; + contributors.forEach(contributor => { + if (!contributorsMap[contributor.login]) { + contributorsMap[contributor.login] = contributor; + } else { + contributorsMap[contributor.login].contributions += contributor.contributions; + } + }); + return Object.values(contributorsMap) + .sort((a, b) => b.contributions - a.contributions); +} \ No newline at end of file diff --git a/website/src/github/sponsors.js b/website/src/github/sponsors.js new file mode 100644 index 000000000..8cebeba1b --- /dev/null +++ b/website/src/github/sponsors.js @@ -0,0 +1,105 @@ +import { graphql } from '@octokit/graphql'; + +const nonGitHubSponsors = [ + { + isActive: true, + sponsorEntity: { + __typename: 'Organization', + login: 'whilenull', + name: 'Null', + websiteUrl: 'https://null.tc/?ref=bref', + }, + isOneTimePayment: false, + }, + { + isActive: true, + sponsorEntity: { + __typename: 'Organization', + login: 'CraftCMS', + name: 'CraftCMS', + websiteUrl: 'https://craftcms.com/?ref=bref.sh', + }, + isOneTimePayment: false, + }, + { + isActive: true, + sponsorEntity: { + __typename: 'Organization', + login: 'ShippyPro', + name: 'ShippyPro', + websiteUrl: 'https://www.shippypro.com/?ref=bref.sh', + }, + isOneTimePayment: false, + }, + { + isActive: true, + sponsorEntity: { + __typename: 'Organization', + login: 'Tideways', + name: 'Tideways', + websiteUrl: 'https://tideways.com/?ref=bref', + }, + isOneTimePayment: false, + }, + { + isActive: true, + sponsorEntity: { + __typename: 'Organization', + login: 'MyBuilder', + name: 'MyBuilder', + websiteUrl: 'https://www.mybuilder.com/?ref=bref.sh', + }, + isOneTimePayment: false, + }, +]; + +export async function getSponsors() { + let githubToken = process.env.GITHUB_TOKEN_READ; + if (!githubToken) { + githubToken = process.env.GITHUB_TOKEN; + } + if (!githubToken) { + throw new Error('Missing GITHUB_TOKEN or GITHUB_TOKEN_READ'); + } + + const sponsorsQuery = ` + { + viewer { + sponsorshipsAsMaintainer(activeOnly: false, first: 100) { + totalCount + edges { + node { + isActive + isOneTimePayment + sponsorEntity { + __typename + ... on User { + name + login + websiteUrl + twitterUsername + } + ... on Organization { + name + login + websiteUrl + twitterUsername + } + } + } + } + } + } + } + `; + const sponsorsResponse = await graphql(sponsorsQuery, { + headers: { + authorization: `token ${githubToken}` + } + }); + + return [ + ...nonGitHubSponsors, + ...sponsorsResponse.viewer.sponsorshipsAsMaintainer.edges.map(({ node }) => node), + ]; +} diff --git a/website/src/pages/404.mdx b/website/src/pages/404.mdx new file mode 100644 index 000000000..6c0a22146 --- /dev/null +++ b/website/src/pages/404.mdx @@ -0,0 +1,75 @@ +import { ChevronRightIcon } from '@heroicons/react/20/solid'; +import { BookOpenIcon, BugAntIcon } from '@heroicons/react/24/solid'; +import { usePlausible } from 'next-plausible'; +import { useEffect } from 'react'; + +export default function NotFound() { + const plausible = usePlausible(); + useEffect(() => { + // Track 404s so we can fix them + plausible('404', { props: { path: document.location.pathname } }); + }, []); + + return
    +
    +
    404
    +

    This page does not + exist

    +
    + Sorry, we couldn’t find the page you’re looking for. +
    +
    +
    +

    Popular pages

    + + +
    +
    +} diff --git a/website/src/pages/_app.jsx b/website/src/pages/_app.jsx new file mode 100644 index 000000000..65efa6f02 --- /dev/null +++ b/website/src/pages/_app.jsx @@ -0,0 +1,40 @@ +import '../../styles/main.css'; +import { useRouter } from 'next/router'; +import { useEffect } from 'react'; +import PlausibleProvider from 'next-plausible'; +import { Inter } from 'next/font/google' +const redirects = require('../../redirects').redirects; + +// See https://nextjs.org/docs/pages/building-your-application/optimizing/fonts#with-tailwind-css +const inter = Inter({ + subsets: ['latin'], + variable: '--font-inter', +}) + +export default function MyApp({ Component, pageProps }) { + // Custom code to redirect old URLs to new ones + // This runs client-side to redirect anchor tags + const router = useRouter(); + useEffect(() => { + // For the initial page load + if (redirects[router.asPath]) { + router.replace(redirects[router.asPath]); + } + // For client-side routing + const onRouteChange = (url) => { + if (redirects[url]) { + router.replace(redirects[url]); + } + } + router.events.on('routeChangeStart', onRouteChange) + return () => { // If the component is unmounted, unsubscribe + router.events.off('routeChangeStart', onRouteChange) + } + }, []); + + return ( + + + + ) +} diff --git a/website/src/pages/_meta.json b/website/src/pages/_meta.json new file mode 100644 index 000000000..72af50810 --- /dev/null +++ b/website/src/pages/_meta.json @@ -0,0 +1,56 @@ +{ + "index": { + "type": "page", + "title": "Bref", + "display": "hidden", + "theme": { + "layout": "raw" + } + }, + "docs": { + "type": "page", + "title": "Documentation" + }, + "news": { + "title": "News", + "type": "page", + "theme": { + "typesetting": "article" + } + }, + "support": { + "type": "page", + "title": "Support", + "theme": { + "layout": "raw" + } + }, + "dashboard": { + "title": "Dashboard", + "type": "page", + "href": "https://dashboard.bref.sh/" + }, + "credits": { + "type": "page", + "title": "Credits", + "display": "hidden", + "theme": { + } + }, + "404": { + "type": "page", + "title": "Sentry integration for AWS Lambda", + "display": "hidden", + "theme": { + "layout": "raw" + } + }, + "sentry": { + "type": "page", + "title": "Sentry integration for AWS Lambda", + "display": "hidden", + "theme": { + "layout": "raw" + } + } +} \ No newline at end of file diff --git a/website/src/pages/credits.mdx b/website/src/pages/credits.mdx new file mode 100644 index 000000000..fb7aaffc2 --- /dev/null +++ b/website/src/pages/credits.mdx @@ -0,0 +1,117 @@ +import { useData } from 'nextra/data'; +import { getContributors, mergeAndSortContributors } from '../github/contributors'; +import { getSponsors } from '../github/sponsors'; +import { NextSeo } from 'next-seo'; + + + +export const getStaticProps = async () => { + let sponsors; + try { + sponsors = await getSponsors(); + } catch (e) { + console.error(e); + return { + props: { + ssg: { + sponsors: [], + contributors: [], + }, + }, + }; + } + + const contributors = mergeAndSortContributors([ + ...(await getContributors('brefphp/bref')), + ...(await getContributors('brefphp/aws-lambda-layers')), + ...(await getContributors('brefphp/laravel-bridge')), + ...(await getContributors('brefphp/symfony-bridge')), + ...(await getContributors('brefphp/extra-php-extensions')), + ...(await getContributors('brefphp/examples')), + ]); + + // See https://nextra.site/docs/guide/ssg + return { + props: { + ssg: { + sponsors, + contributors, + }, + }, + // This page will be generated statically on deployment and never regenerated + revalidate: false, + }; +} + +export const SponsorsList = ({ sponsors }) => { + const sortedSponsors = sponsors + // Fill missing names with the login + .map(sponsor => { + sponsor.sponsorEntity.name = sponsor.sponsorEntity.name ?? sponsor.sponsorEntity.login; + return sponsor; + }) + // Sport organizations first, then alphabetically by name second + .sort(({ sponsorEntity: a }, { sponsorEntity: b }) => { + // Same type + if (a.__typename === b.__typename) { + return a.name.localeCompare(b.name); + } + // Different types + return a.__typename === 'Organization' ? -1 : 1; + }); + return

    + {sortedSponsors.map(({ sponsorEntity, isOneTimePayment }, i) => ( + + {i > 0 && ", "} + + {sponsorEntity.name} + + {isOneTimePayment && ' (one-time sponsor)'} + + ))} + . +

    ; +}; + +export const ContributorsList = () => { + const { contributors } = useData(); + return

    + {contributors.map(({ login, html_url, contributions }, i) => ( + + {i > 0 && ", "} + + {login} + + {contributions > 1 && ` (${contributions})`} + + ))} + . +

    ; +}; + +export const CurrentSponsors = () => { + const { sponsors } = useData(); + return isActive)} />; +}; + +export const PastSponsors = () => { + const { sponsors } = useData(); + return !isActive)} />; +}; + +# Credits + +Here are the amazing people behind Bref. Either they are sponsoring Bref or they have contributed to the project. + +## Current sponsors + + + +## Contributors + + + +## Past sponsors + + diff --git a/website/src/pages/index.mdx b/website/src/pages/index.mdx new file mode 100644 index 000000000..7cfe6292d --- /dev/null +++ b/website/src/pages/index.mdx @@ -0,0 +1,129 @@ +import Link from 'next/link'; +import { LaravelFullIcon } from '../components/icons/LaravelFullIcon'; +import { SymfonyFullIcon } from '../components/icons/SymfonyFullIcon'; +import styles from './index.module.css'; +import Companies from '../components/home/companies'; +import Testimonials from '../components/home/testimonials'; +import Invocations from '../components/home/invocations'; +import UseCases from '../components/home/use-cases'; +import Sponsors from '../components/home/sponsors'; +import Intro from '../components/home/intro'; +import { useData } from 'nextra/data'; +import { getBrefInvocations } from '../aws/invocations'; +import { NextSeo } from 'next-seo'; + + + +export async function getStaticProps() { + let invocations; + try { + invocations = await getBrefInvocations(); + } catch (e) { + console.error(e); + // Fallback value for local development, preview environments, etc. (no permissions) + invocations = 11700607900; + } + // See https://nextra.site/docs/guide/ssg + return { + props: { + ssg: { + invocations: invocations, + }, + }, + // The page will be considered as stale and regenerated every hour + revalidate: 60 * 60, + }; +} + +
    +
    +
    +
    +
    +

    + Simple + and scalable PHP with + serverless +

    +
    + Simplify your infrastructure and scale with ease. +
    +
    + Bref is an **open-source** project that helps you go serverless on AWS with PHP. +
    +
    + + Documentation + + + View on GitHub + +
    +
    + + +
    +
    +
    +
    +
    +