├── .devcontainer ├── devcontainer.json ├── ncu.sh └── setup.sh ├── .github ├── dependabot.yml └── workflows │ └── deploy.yaml ├── .gitignore ├── .vscode └── settings.json ├── CHANGELOG.md ├── README.md ├── functions ├── codeScanningToSlack │ ├── .eslintignore │ ├── .eslintrc │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── main.ts │ │ ├── messages │ │ │ ├── closedByUser.ts │ │ │ ├── created.ts │ │ │ ├── fixed.ts │ │ │ └── index.ts │ │ ├── ssm.ts │ │ └── verify.ts │ ├── tsconfig.json │ └── types │ │ └── common │ │ ├── main.d.ts │ │ └── package.json └── githubWebhookIPValidator │ ├── .eslintignore │ ├── .eslintrc │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── checkIPs.ts │ ├── getGitHubAppJWT.ts │ ├── getIPs.ts │ ├── main.ts │ └── ssm.ts │ ├── tsconfig.json │ └── types │ └── common │ ├── main.d.ts │ └── package.json ├── package.json ├── template.yml └── yarn.lock /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nickliffen/codescanningtoslack", 3 | "image": "ghcr.io/nickliffen/csenv:main", 4 | "settings": {}, 5 | "extensions": [ 6 | "dbaeumer.vscode-eslint", 7 | "hookyqr.beautify", 8 | "naumovs.color-highlight", 9 | "redhat.vscode-yaml", 10 | "vscode-icons-team.vscode-icons", 11 | "wayou.vscode-todo-highlight", 12 | "esbenp.prettier-vscode", 13 | "ms-vscode.vscode-typescript-next", 14 | "github.copilot", 15 | "donjayamanne.githistory", 16 | "nixon.env-cmd-file-syntax" 17 | ], 18 | "remoteUser": "root" 19 | } 20 | -------------------------------------------------------------------------------- /.devcontainer/ncu.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function setup { 4 | for d in */ ; do 5 | [ -L "${d%/}" ] && continue 6 | echo "Upgrading Dependencies in: $d" 7 | cd "$d" 8 | ncu -u 9 | npm install 10 | cd .. 11 | done 12 | } 13 | 14 | cd /workspaces/CodeScanningToSlack/functions 15 | 16 | setup 17 | -------------------------------------------------------------------------------- /.devcontainer/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function setup { 4 | for d in */ ; do 5 | [ -L "${d%/}" ] && continue 6 | echo "Processing $d" 7 | cd "$d" 8 | npm install 9 | npm run format 10 | npm run build 11 | cd .. 12 | done 13 | } 14 | 15 | cd /workspaces/CodeScanningToSlack/functions 16 | 17 | setup 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: monthly 7 | reviewers: 8 | - "nickliffen" 9 | groups: 10 | dependencies: 11 | patterns: 12 | - "*" 13 | - package-ecosystem: npm 14 | directory: / 15 | schedule: 16 | interval: monthly 17 | reviewers: 18 | - "nickliffen" 19 | groups: 20 | dependencies: 21 | patterns: 22 | - "*" 23 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | workflow_dispatch: 9 | 10 | permissions: 11 | id-token: write # This is required for requesting the JWT 12 | contents: read # This is required for actions/checkout 13 | 14 | jobs: 15 | build-deploy: 16 | runs-on: ubuntu-latest 17 | env: 18 | REGION: us-east-1 19 | environment: main 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: actions/setup-node@v4 23 | with: 24 | node-version: "20" 25 | - uses: actions/setup-python@v5 26 | with: 27 | python-version: "3.12" 28 | 29 | - name: Extract branch name 30 | id: extract_branch 31 | shell: bash 32 | run: | 33 | echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT 34 | 35 | - name: Clean up Branch Name (Valid for CloudFormation Stack Name) 36 | id: formatted_branch 37 | env: 38 | DATA: ${{ steps.extract_branch.outputs.branch }} 39 | run: | 40 | noSpecialChars="$(echo "$DATA" | sed 's/[^a-zA-Z0-9 ]//g')" # Removing any character apart from numbers letters and spaces 41 | lowercase="$(echo "$noSpecialChars" | awk '{print tolower($0)}')" # Making Lowercase 42 | lowercaseDashRepoName="$(echo ${lowercase// /-})" # Replacing spaces with dashses 43 | 44 | if [ ${#lowercaseDashRepoName} -gt 100 ]; then 45 | lowercaseDashRepoName=${lowercaseDashRepoName:0:100} # Limiting to only 100 characters 46 | fi 47 | 48 | echo "BRANCH=$(echo "$lowercaseDashRepoName")" >> "$GITHUB_OUTPUT" 49 | 50 | - name: Echo'ing Branch Names (For Debugging) 51 | run: | 52 | echo ${{ steps.formatted_branch.outputs.BRANCH }} 53 | echo ${{ steps.extract_branch.outputs.branch }} 54 | 55 | - name: Setup Cloud Formation Linter with Latest Version 56 | uses: scottbrenner/cfn-lint-action@v2 57 | - name: Print the Cloud Formation Linter Version & Run Linter. 58 | run: | 59 | cfn-lint --version 60 | cfn-lint -t ./template.yml 61 | - uses: aws-actions/setup-sam@v2 62 | - uses: aws-actions/configure-aws-credentials@v4 63 | with: 64 | audience: sts.amazonaws.com 65 | aws-region: ${{ env.REGION }} 66 | role-to-assume: arn:aws:iam::377117578606:role/CodeScanningToSlack 67 | - run: | 68 | cd ./functions/codeScanningToSlack 69 | npm cache clean --force 70 | npm install 71 | npm run build 72 | - run: | 73 | cd ./functions/githubWebhookIPValidator 74 | npm cache clean --force 75 | npm install 76 | npm run build 77 | - run: sam build --use-container 78 | - run: sam deploy --debug --no-confirm-changeset --no-fail-on-empty-changeset --stack-name CodeScanningToSlackStack-${{ steps.formatted_branch.outputs.BRANCH }} --capabilities CAPABILITY_IAM --region ${{ env.REGION }} --resolve-s3 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Custom 3 | lib/ 4 | file:/ 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | .pnpm-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # Snowpack dependency directory (https://snowpack.dev/) 50 | web_modules/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variables file 77 | .env 78 | .env.test 79 | .env.production 80 | 81 | # parcel-bundler cache (https://parceljs.org/) 82 | .cache 83 | .parcel-cache 84 | 85 | # Next.js build output 86 | .next 87 | out 88 | 89 | # Nuxt.js build / generate output 90 | .nuxt 91 | dist 92 | 93 | # Gatsby files 94 | .cache/ 95 | # Comment in the public line in if your project uses Gatsby and not Next.js 96 | # https://nextjs.org/blog/next-9-1#public-directory-support 97 | # public 98 | 99 | # vuepress build output 100 | .vuepress/dist 101 | 102 | # Serverless directories 103 | .serverless/ 104 | 105 | # FuseBox cache 106 | .fusebox/ 107 | 108 | # DynamoDB Local files 109 | .dynamodb/ 110 | 111 | # TernJS port file 112 | .tern-port 113 | 114 | # Stores VSCode versions used for testing VSCode extensions 115 | .vscode-test 116 | 117 | # yarn v2 118 | .yarn/cache 119 | .yarn/unplugged 120 | .yarn/build-state.yml 121 | .yarn/install-state.gz 122 | .pnp.* 123 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "yaml.schemas": { 3 | "https://json.schemastore.org/github-workflow.json": "file:///workspaces/CodeScanningToSlack/.github/workflows/deploy.yaml" 4 | } 5 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [v1.0.0](https://github.com/NickLiffen/CodeScanningToSlack/releases/v1.0.0) - 2021-08-25 9 | 10 | - Lambda Function that Validates Webhooks are from GitHub 11 | - Lambda Function that proxies results from GitHub to Slack 12 | - GitHub Workflow that deploys solution to AWS. 13 | - Infrastructure as Code writtin in Cloud Formation 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Connecting GitHub Code Scanning Alerts to Slack 2 | 3 | **TLDR**: Right now, `code_scanning_alerts` are not supported by GitHub Actions events. This means we cannot use GitHub Actions for this use case. Once `code_scanning_alerts` are supported, a GitHub Action will be created for teams who do not want to deploy a custom solution. 4 | 5 | ## Overview 6 | 7 | A solution that enables organizations to filter GitHub Code Scanning alerts into Slack channels. Specifically whenever an alert has been: 8 | 9 | - Created 10 | - Fixed 11 | - Manually Closed by a User 12 | 13 | The solution has been designed to be configurable to the end-users preferences when it comes to being notified. Code Scanning can be _noisy_, so the solution ensures you can configure when you get notified. 14 | 15 | This solution is deployed to AWS. 16 | 17 | ## How this works 18 | 19 | Whenever a new code scanning alert is: `created`, `fixed` or `closed_by_user`, a webhook from a GitHub App will be sent to an API Gateway within AWS. The API's first step is passing the context of the payload to a Lambda Authorizer, ensuring the Webhook has come from GitHub. If valid, the API will pass the event payload (from the webhook) to a Lambda for processing. The first step this Lambda does is validate the GitHub Secret is correct. If valid, the Lambda will destructure the payload, find the event type (`created`, `fixed` or `closed_by_user`) and, based on the event, send a specific message to a Slack App Incoming Webhook URL. The Slack App will then forward the message to the specific channels configured on the Slack App. 20 | 21 | At any point, if the webhook IP or secret sent do not match or are not valid, an unauthorized response will be sent to the client. 22 | 23 | ## Technologies Used 24 | 25 | The following technologies are used throughout this solution: 26 | 27 | - AWS 28 | - [Lambda](https://aws.amazon.com/lambda/) is used for compute power. 29 | - [Cloud Formation](https://aws.amazon.com/cloudformation/) is used as our IaC (Infrastructure as Code). 30 | - [HTTP API Gateway](https://aws.amazon.com/api-gateway/) is used for ingress into AWS. 31 | - [Cloud Watch](https://aws.amazon.com/cloudwatch/) is used for logging and monitoring. 32 | - [IAM](https://aws.amazon.com/iam/) is used to connect resources and allow deployments into AWS from GitHub Actions 33 | - [S3](https://aws.amazon.com/s3/) is used by AWS SAM to deploy the stack, and therefore deploy it into the AWS ecosystem using Cloud Formation. 34 | - [AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) is used to store parameters. 35 | - Slack 36 | - [Slack App](https://nickliffentes-6sx7712.slack.com/intl/en-gb/apps) is used as our ingress method into Slack. 37 | - GitHub 38 | - [GitHub App](https://docs.github.com/en/developers/apps/building-github-apps) is used as our egress method out of GitHub. 39 | - [GitHub Actions](https://docs.github.com/en/developers/apps/building-github-apps) is used to deploy the solution into AWS. 40 | 41 | [AWS SAM](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) is used for the Lambda & HTTP API Gateway resources. 42 | 43 | **Note**: Even though this solution is deployed to AWS, the code can be changed to work with the likes of Azure and GCP (Azure Function, Google Functions, etc.). 44 | 45 | ## Architecture 46 | 47 | ![GitHub Code Scanning to Slack Architecure](https://lucid.app/publicSegments/view/c953c8c6-5a7e-4a1d-9e5e-be0838e156eb/image.png) 48 | https://i.ibb.co/MnJ60KZ/Example.png 49 | 50 | ## Pre-Req's 51 | 52 | 1. Access to an AWS environment. 53 | 2. Access to a Slack environment. 54 | 3. Access to a GitHub environment. 55 | 4. A repository where the code for this solution is going to live. 56 | 57 | ## Getting Started 58 | 59 | The below steps show the _path of least resistance_ way of deploying this solution into AWS. There are many ways to do this. Every organization likely has different processes (especially with deploying into AWS), meaning you may have to pivot during these steps to accommodate organization-specific processes. This is okay. Please treat these instructions as an example and reference; if they work end-to-end, great; if not, please adjust to your company policies. 60 | 61 | If you get an error you cannot get around, please log an issue on this repository. 62 | 63 | ### Step One: Create IAM User 64 | 65 | Create an [IAM User](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html). The IAM User will need to have the capability to do the following: 66 | 67 | - CRUD access over S3 Resources. 68 | - CRUD access over IAM Resources. 69 | - CRUD access over API Gateway Resources. 70 | - CRUD acess over Lambda Resources. 71 | - CRUD access over CloudWatch Resources. 72 | 73 | From that user, create an AWS Access key and secret. Once you have both, create a [GitHub Enviroment](https://docs.github.com/en/actions/reference/environments#creating-an-environment) called **main** and within that environment create two secrets `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` with the relevant information from AWS in. Set the environment to only deploy from the `main` branch. (This can be changed later at any time). 74 | 75 | **NOTE**: If your organization doesn't allow the use of IAM Users, this isn't a problem. We use the official [configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) GitHub action. Meaning you can head to the `.github/workflows/deploy.yaml` file and swap out the AWS User method to assuming an AWS Role. Or, if you have a custom GitHub Action which authenticates into AWS, remove the `configure-AWS-credentials` action and swap it out for your custom one. 76 | 77 | ### Step Two: Create and Configure Slack App 78 | 79 | Create a [Slack Application](https://api.slack.com/apps). You will need to be an administrator of your Slack organization to do this. When you create the Slack application, add the channel(s) you would like the Code Scanning results to be posted to. 80 | 81 | Instructions on doing this can be found here: [Webhooks: Getting Started](https://api.slack.com/messaging/webhooks#getting_started__1.-create-a-slack-app-if-you-dont-have-one-already). 82 | 83 | You should end up with a URL that looks like this: `https://hooks.slack.com/services/******/******/******` (I have redacted the values for `***`). 84 | 85 | **NOTE**: Don't share this URL with anyone; keep it private. 86 | 87 | ### Step Three: Create and Configure GitHub App 88 | 89 | Create a [GitHub Application](https://docs.github.com/en/developers/apps/building-github-apps/creating-a-github-app). You will need to be an administrator of your GitHub organization to do this. During the creation of the application, you only need to enter: 90 | 91 | 1. GitHub App Name: GitHub Code Scanning Alerts to Slack 92 | 2. Homepage URL: https://donotknowthisurlyet.com 93 | 3. Webhook URL: https://donotknowthisurlyet.com 94 | 4. Webhook Secret: _enter secret of your choice - keep this value secret but note it down for later_ 95 | 5. Permissions: 96 | - Security event 97 | 6. Subscribe to events: 98 | - Code scanning alert 99 | 7. Where can this integration be installed: Only on this account 100 | 101 | The rest of the fields you do not need to enter. Right now, you don't know what the URL's are going to be, so put any value in there. 102 | 103 | Once the application is created, you need to install the GitHub App on your organization and then add the repositories you want Code Scanning events to be sent to Slack. Follow the instructions here: [Installing your private GitHub App on your repository](https://docs.github.com/en/developers/apps/managing-github-apps/installing-github-apps#installing-your-private-github-app-on-your-repository). 104 | 105 | **NOTE**: When you install the GitHub App on your GitHub Organisation, I would advise you do not have it connected to every repository. It will get **very** noisy. Only install it on the repositories you are interested in. 106 | 107 | Once it's installed, we need to collect some information: 108 | 109 | 1. GitHub App Private Key. Follow the instructions here: [Generating a private key](https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps#generating-a-private-key) to do that. 110 | 2. Client Secret: Just above where you generated the private key, there will be an option for you to generate a client secret. Click the _Generate a new Client Secret_ button and note down the secret. 111 | 3. Client ID: Just above where you generated the client secret, you will see the Client ID; take a note of the id. 112 | 4. App ID: Just above where you generated the client secret, you will see the App ID; take a note of the id. 113 | 5. Installation ID: The Installation ID is in a different location; head to your Organizations GitHub App's page (https://github.com/organizations/${orgName}/settings/installations). Click _Configure_ next to the GitHub App you created. If you look at the URL, at the end of the URL, you will see a number. It should be after the `installations/` part of the URL. Copy down that number. 114 | 115 | ### Step Four: Create Parameters within AWS Systems Manager (Parameter Store) 116 | 117 | Log into AWS, head to AWS Systems Manager, then AWS Parameter Store. In total, you will need to create seven parameters. 118 | 119 | 1. `/code scanning/APP_CLIENT_ID`: The GitHub App Client ID you got from Step Three. 120 | 2. `/code scanning/APP_CLIENT_SECRET`: The GitHub App Client Secret you got from Step Three. 121 | 3. `/code scanning/APP_ID`: The GitHub App ID you got from Step Three. 122 | 4. `/code scanning/APP_INSTALLATION_ID`: The GitHub App Installation ID you got from Step Three. 123 | 5. `/code scanning/APP_PRIVATE_KEY`: The GitHub App Private Key you got from Step Three. 124 | 6. `/code scanning/GITHUB_WEBHOOKS_SECRET`: The GitHub App Private Key you got from Step Three. (The first part when you created the GitHub App) 125 | 7. `/code scanning/SLACK_WEBHOOK_URL`: The Slack Webhook URL you got at the end of Step Two. 126 | 127 | **NOTE**: It is recommended you make the: `/codescanning/APP_CLIENT_SECRET`, `/codescanning/APP_PRIVATE_KEY`, `/codescanning/GITHUB_WEBHOOKS_SECRET` and `/codescanning/SLACK_WEBHOOK_URL` values `SecureString` within Parameter Store. The rest can be simply `String` types. 128 | 129 | ### Step five: Deployment into AWS 130 | 131 | Second to last step! Before we do this, let's check a few things: 132 | 133 | - An environment is created with two GitHub Secrets in which can deploy to AWS. 134 | A slack app is created with an incoming webhook URL that can drop messages into the channels of your choice. 135 | - A GitHub app is created, connected to the repositories where you would like to receive code scanning alerts from. 136 | - AWS Parameters have been created. 137 | 138 | If the above is complete, pull the contents of this codebase and push it into the repository where you configured the GitHub Environment and Secrets. Make sure you push to the main branch (or the branch you configured in the environment to deploy from). 139 | 140 | GitHub Actions should now trigger! You can watch the workflow within the Actions tab of your repository, but what it is doing is: 141 | 142 | - Linting 143 | - Building (Typescript -> Javascript) 144 | - Building (SAM) 145 | - Deploying (SAM) 146 | 147 | The first time you deploy, it should take about 5-6 minutes. As long as the role you created in Step One has the correct permissions mentioned above, your deployment should succeed. Log into AWS, head to Cloud Formation, look for the `codeScanning` stack, head to outputs, and you should see an output called: `HttpApiUrl`. Note down this URL. 148 | 149 | ### Step Six: Update GitHub App to send webhooks to the URL output from Step Five 150 | 151 | Head back to the GitHub App you created in Step Four. Head down to the Webhook URL, enter the URL from Step Five and add `/codescanning` onto the end of the URI. The URL you got from the output is the domain, but not the full URI where webhooks should be sent. So make sure to put the `/codescanning` endpoint onto that URL. 152 | 153 | Click _Save_ 154 | 155 | Done! From now on, whenever a Code Scanning Alert gets: `created`, `fixed` and `closed_by_user`, a notification will get dropped into Slack. 156 | 157 | ## Example 158 | 159 | The below shows an example of what a message would look like in Slack. This specific message is when an alert gets manually closed. 160 | 161 | ![Example Showing a Code Scanning Event being Closed](https://i.ibb.co/MnJ60KZ/Example.png) 162 | 163 | ## FAQ's 164 | 165 | ### I don't use AWS!? How can I use this solution? 166 | 167 | Not a problem. The reason why AWS was chosen is due to the market popularity. However, we understand that not every company has AWS. The codebase will require some reconfiguration to meet whatever requirements your cloud/hosting provider has. The codebase structure can stay the same; you will likely have to change the `template.yml`, for example. 168 | 169 | I would advise if you don't use AWS. Use this codebase as a reference. It is a great template to _copy and paste_ snippets from and put into your solution. 170 | 171 | ### I don't use GitHub Actions!? How can I use this solution? 172 | 173 | Again, not a problem. Take a look at the `.github/workflow/deploy.yaml` and translate that to whatever CI engine you are using. You shouldn't need to make any changes to the actual codebase, just the workflow file. 174 | 175 | ### I have the solution working, but I would like to change the format of the messages. How can I do this? 176 | 177 | Great question! This solution has been designed to be configurable. Head to the following directory: `functions/codeScanningToSlack/src/messages`. Here you will see the pre-created messages. These are just templates. We **welcome** you to go in and change the structure of the message to suit your needs. 178 | 179 | If you would like to add more messages, outside of the three pre-set: (`created`, `fixed` or `closed_by_user`) ones. Create a new file within the `messages` directory. The format should be similar to the other messages, but with the text you need, then head back to the `functions/codeScanningToSlack/src/main.ts` file and add the file with the message, connected to whatever action you would like the message to be sent from. E.G `opened_by_user`. 180 | 181 | The different events can be found under the `action` key here: [code_scanning_alert](https://docs.github.com/en/enterprise-server@2.22/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#code_scanning_alert). 182 | 183 | ### I have a question or an issue that isn't answered in this README. Where can I ask it? 184 | 185 | Open an [issue](https://github.com/NickLiffen/CodeScanningToSlack/issues/new) within this repository. 186 | 187 | ## Contributions 188 | 189 | This repository 100% welcomes contributions! Would you mind logging an issue to discuss what you would like to implement first, then raise a pull request when it's ready for review? 190 | -------------------------------------------------------------------------------- /functions/codeScanningToSlack/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | lib 4 | coverage -------------------------------------------------------------------------------- /functions/codeScanningToSlack/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint"], 5 | "extends": [ 6 | "eslint:recommended", 7 | "plugin:@typescript-eslint/eslint-recommended", 8 | "plugin:@typescript-eslint/recommended" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /functions/codeScanningToSlack/README.md: -------------------------------------------------------------------------------- 1 | # GitHub Code Scanning Webhook to Slack 2 | 3 | ### Purpose 4 | 5 | The purpose of this lambda is to take a code scanning webhook, manipulate the webhook into a structured Slack webhook response, and send the body of that response to a Slack channel. 6 | 7 | The aim is to help make code scanning notifications more consumable. Although developers live within GitHub, they also communicate often in Slack. We would like to bring the data to as many different usable endpoints as possible. Slack being one. 8 | -------------------------------------------------------------------------------- /functions/codeScanningToSlack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code-scanning-to-slack", 3 | "version": "1.0.0", 4 | "description": "Lambda which proxies code scanning results from GitHub to Slack", 5 | "scripts": { 6 | "build": "npx tsc", 7 | "format": "npx prettier --write '**/*.ts'", 8 | "format-check": "npx prettier --check '**/*.ts'", 9 | "lint": "npx eslint . --ext .ts", 10 | "lint-fix": "npx eslint --fix . --ext .ts" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@tsconfig/node20": "^20.1.2", 17 | "@types/aws-lambda": "^8.10.125", 18 | "@types/json-schema": "^7.0.14", 19 | "@types/node": "^20.8.10", 20 | "@typescript-eslint/eslint-plugin": "^6.9.1", 21 | "@typescript-eslint/parser": "^6.9.1", 22 | "eslint": "^8.53.0", 23 | "eslint-config-prettier": "^9.0.0", 24 | "eslint-plugin-prettier": "^5.0.1", 25 | "prettier": "^3.0.3", 26 | "ts-node": "^10.9.1", 27 | "tslib": "^2.6.2", 28 | "typescript": "^5.2.2" 29 | }, 30 | "engines": { 31 | "node": "20" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "https://github.com/NickLiffen/CodeScanningToSlack" 36 | }, 37 | "dependencies": { 38 | "@aws-sdk/client-ssm": "^3.441.0", 39 | "@octokit/webhooks-methods": "^4.0.0", 40 | "@slack/webhook": "^7.0.1" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /functions/codeScanningToSlack/src/main.ts: -------------------------------------------------------------------------------- 1 | import { ssm } from "./ssm"; 2 | import { IncomingWebhook, IncomingWebhookSendArguments } from "@slack/webhook"; 3 | import { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from "aws-lambda"; 4 | import { secretVerifier } from "./verify"; 5 | import { fixedMessage, createdMessage, closedByUserMessage } from "./messages"; 6 | 7 | export const handler = async ( 8 | event: APIGatewayProxyEventV2, 9 | ): Promise => { 10 | try { 11 | await ssm(); 12 | 13 | const response = (await secretVerifier(event)) as boolean; 14 | 15 | if (!response) 16 | return { 17 | statusCode: 401, 18 | body: "webhook secret provided does not match. unauthorized.", 19 | }; 20 | 21 | const body = event.body || ""; 22 | 23 | const { action, alert, repository, organization, ref, commit_oid } = 24 | JSON.parse(body); 25 | 26 | let IncomingWebhookSendArguments: IncomingWebhookSendArguments = {}; 27 | 28 | const refsToBeNotifiedAbout = [ 29 | "refs/heads/main", 30 | "refs/heads/dev", 31 | "refs/heads/staging", 32 | ] as string[]; 33 | 34 | if (action === "closed_by_user") { 35 | IncomingWebhookSendArguments = await closedByUserMessage( 36 | alert, 37 | repository, 38 | organization, 39 | ); 40 | } 41 | 42 | if (action === "created" && refsToBeNotifiedAbout.includes(ref)) { 43 | IncomingWebhookSendArguments = await createdMessage( 44 | alert, 45 | repository, 46 | organization, 47 | commit_oid, 48 | ); 49 | } 50 | 51 | if (action === "fixed" && refsToBeNotifiedAbout.includes(ref)) { 52 | IncomingWebhookSendArguments = await fixedMessage( 53 | alert, 54 | repository, 55 | organization, 56 | commit_oid, 57 | ); 58 | } 59 | 60 | const url = process.env.SLACK_WEBHOOK_URL as string; 61 | const webhook = new IncomingWebhook(url); 62 | await webhook.send(IncomingWebhookSendArguments); 63 | 64 | return { statusCode: 200, body: "Successfully Posted Message to Slack!" }; 65 | } catch (e: any) { 66 | const body = e.message || ""; 67 | console.error(e); 68 | return { statusCode: 401, body }; 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /functions/codeScanningToSlack/src/messages/closedByUser.ts: -------------------------------------------------------------------------------- 1 | import { IncomingWebhookSendArguments } from "@slack/webhook"; 2 | export const closedByUserMessage = async ( 3 | alert: any, 4 | repository: any, 5 | organization: any, 6 | ): Promise => { 7 | return { 8 | text: `A Code Scanning alert has just manually been closed. It has been closed by ${alert.dismissed_by.login}. The repository where it has been closed is ${repository.name} (within the ${organization.login} organisation). Information about the closed alert can be found below.`, 9 | attachments: [ 10 | { 11 | color: "warning", 12 | title: `${alert.rule.id}`, 13 | title_link: `${alert.html_url}`, 14 | fields: [ 15 | { 16 | title: "Alert Description", 17 | value: `${alert.rule.description}`, 18 | short: true, 19 | }, 20 | { 21 | title: "Dismissed Reason", 22 | value: `${alert.dismissed_reason}`, 23 | short: true, 24 | }, 25 | { 26 | title: "Alert Severity:", 27 | value: `${alert.rule.severity}`, 28 | short: true, 29 | }, 30 | ], 31 | }, 32 | ], 33 | } as IncomingWebhookSendArguments; 34 | }; 35 | -------------------------------------------------------------------------------- /functions/codeScanningToSlack/src/messages/created.ts: -------------------------------------------------------------------------------- 1 | import { IncomingWebhookSendArguments } from "@slack/webhook"; 2 | export const createdMessage = async ( 3 | alert: any, 4 | repository: any, 5 | organization: any, 6 | commit_oid: string, 7 | ): Promise => { 8 | return { 9 | text: `A Code Scanning alert from ${alert.tool.name} has just been found and created. The repository where the alert has been found is ${repository.name} (within the ${organization.login} organisation). Information about the alert can be found below.`, 10 | attachments: [ 11 | { 12 | color: "danger", 13 | title: `${alert.rule.id}`, 14 | title_link: `${alert.html_url}`, 15 | fields: [ 16 | { 17 | title: "Rule ID", 18 | value: `${alert.rule.id}`, 19 | short: true, 20 | }, 21 | { 22 | title: "Rule Description", 23 | value: `${alert.rule.description}`, 24 | short: true, 25 | }, 26 | { 27 | title: "Alert Severity:", 28 | value: `${alert.rule.severity}`, 29 | short: true, 30 | }, 31 | { 32 | title: "Commit Found In:", 33 | value: `${commit_oid}`, 34 | short: true, 35 | }, 36 | ], 37 | }, 38 | ], 39 | } as IncomingWebhookSendArguments; 40 | }; 41 | -------------------------------------------------------------------------------- /functions/codeScanningToSlack/src/messages/fixed.ts: -------------------------------------------------------------------------------- 1 | import { IncomingWebhookSendArguments } from "@slack/webhook"; 2 | export const fixedMessage = async ( 3 | alert: any, 4 | repository: any, 5 | organization: any, 6 | commit_oid: string, 7 | ): Promise => { 8 | return { 9 | text: `Yay! A Code Scanning alert found by ${alert.tool.name} has just been fixed . The repository where it has been fixed is ${repository.name} (within the ${organization.login} organisation). Information about the closed alert can be found below.`, 10 | attachments: [ 11 | { 12 | color: "good", 13 | title: `${alert.rule.id}`, 14 | title_link: `${alert.html_url}`, 15 | fields: [ 16 | { 17 | title: "Alert Description", 18 | value: `${alert.rule.description}`, 19 | short: true, 20 | }, 21 | { 22 | title: "Commit Fixed In:", 23 | value: `${commit_oid}`, 24 | short: true, 25 | }, 26 | { 27 | title: "Alert Severity:", 28 | value: `${alert.rule.severity}`, 29 | short: true, 30 | }, 31 | ], 32 | }, 33 | ], 34 | } as IncomingWebhookSendArguments; 35 | }; 36 | -------------------------------------------------------------------------------- /functions/codeScanningToSlack/src/messages/index.ts: -------------------------------------------------------------------------------- 1 | import { fixedMessage } from "./fixed"; 2 | import { createdMessage } from "./created"; 3 | import { closedByUserMessage } from "./closedByUser"; 4 | 5 | export { fixedMessage, createdMessage, closedByUserMessage }; 6 | -------------------------------------------------------------------------------- /functions/codeScanningToSlack/src/ssm.ts: -------------------------------------------------------------------------------- 1 | import { SSMClient, GetParametersByPathCommand } from "@aws-sdk/client-ssm"; 2 | 3 | export const ssm = async (): Promise => { 4 | const region = process.env.REGION ? process.env.REGION : "us-east-1"; 5 | const client = new SSMClient({ region }); 6 | const command = new GetParametersByPathCommand({ 7 | Path: "/codescanning", 8 | WithDecryption: true, 9 | }); 10 | 11 | try { 12 | const { Parameters } = await client.send(command); 13 | 14 | if (Parameters) { 15 | Parameters.forEach((param) => { 16 | const name = param.Name ? param.Name.replace("/codescanning/", "") : ""; 17 | const value = param.Value ? param.Value : ""; 18 | process.env[name] = value; 19 | }); 20 | } 21 | } catch (err) { 22 | console.error("Error within function (ssm)", err); 23 | throw err; 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /functions/codeScanningToSlack/src/verify.ts: -------------------------------------------------------------------------------- 1 | import { verify } from "@octokit/webhooks-methods"; 2 | import { APIGatewayProxyEventV2 } from "aws-lambda"; 3 | 4 | export const secretVerifier = async ( 5 | event: APIGatewayProxyEventV2, 6 | ): Promise => { 7 | try { 8 | const body = event.body as string; 9 | const signature = event.headers["x-hub-signature-256"] as string; 10 | const authedAnswer = await verify( 11 | process.env.GITHUB_WEBHOOKS_SECRET, 12 | body, 13 | signature, 14 | ); 15 | return authedAnswer; 16 | } catch (err) { 17 | console.error("Error within function (secretVerifier)", err); 18 | throw err; 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /functions/codeScanningToSlack/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node20/tsconfig.json", 3 | "include": ["./src/*.ts"], 4 | "exclude": ["lib", "node_modules"], 5 | "compilerOptions": { 6 | "typeRoots": ["./types", "node_modules/@types"], 7 | "outDir": "./lib", 8 | "noUnusedLocals": true, 9 | "noUnusedParameters": true, 10 | "noImplicitAny": true, 11 | "removeComments": false, 12 | "preserveConstEnums": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /functions/codeScanningToSlack/types/common/main.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace NodeJS { 2 | export interface ProcessEnv { 3 | SLACK_URL: string; 4 | GITHUB_WEBHOOKS_SECRET: string; 5 | } 6 | } 7 | 8 | type SLACK_URL = string; 9 | -------------------------------------------------------------------------------- /functions/codeScanningToSlack/types/common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "common", 3 | "version": "1.0.0", 4 | "typings": "main.d.ts" 5 | } 6 | -------------------------------------------------------------------------------- /functions/githubWebhookIPValidator/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | lib 4 | coverage -------------------------------------------------------------------------------- /functions/githubWebhookIPValidator/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint"], 5 | "extends": [ 6 | "eslint:recommended", 7 | "plugin:@typescript-eslint/eslint-recommended", 8 | "plugin:@typescript-eslint/recommended" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /functions/githubWebhookIPValidator/README.md: -------------------------------------------------------------------------------- 1 | # GitHub Webhook IP Validator 2 | 3 | ### Purpose 4 | 5 | The purpose of this Lambda is to validate that the IP coming from the request is a valid IP Addresses; listed under the [Meta Endpoint](https://api.github.com/meta). 6 | 7 | This is a Lambda authorizer. 8 | -------------------------------------------------------------------------------- /functions/githubWebhookIPValidator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-webhook-ip-validator", 3 | "version": "1.0.0", 4 | "description": "Lambda which validates the webhook comes from GitHub", 5 | "scripts": { 6 | "build": "npx tsc", 7 | "format": "npx prettier --write '**/*.ts'", 8 | "format-check": "npx prettier --check '**/*.ts'", 9 | "lint": "npx eslint . --ext .ts", 10 | "lint-fix": "npx eslint --fix . --ext .ts" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@tsconfig/node20": "^20.1.2", 17 | "@types/aws-lambda": "^8.10.125", 18 | "@types/ip": "^1.1.2", 19 | "@types/json-schema": "^7.0.14", 20 | "@types/node": "^20.8.10", 21 | "@typescript-eslint/eslint-plugin": "^6.9.1", 22 | "@typescript-eslint/parser": "^6.9.1", 23 | "eslint": "^8.53.0", 24 | "eslint-config-prettier": "^9.0.0", 25 | "eslint-plugin-prettier": "^5.0.1", 26 | "prettier": "^3.0.3", 27 | "ts-node": "^10.9.1", 28 | "tslib": "^2.6.2", 29 | "typescript": "^5.2.2" 30 | }, 31 | "engines": { 32 | "node": "20" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "https://github.com/NickLiffen/CodeScanningToSlack" 37 | }, 38 | "dependencies": { 39 | "@aws-sdk/client-ssm": "^3.441.0", 40 | "@octokit/auth-app": "^6.0.1", 41 | "@octokit/graphql": "^7.0.2", 42 | "ip": "^1.1.8" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /functions/githubWebhookIPValidator/src/checkIPs.ts: -------------------------------------------------------------------------------- 1 | import { cidrSubnet } from "ip"; 2 | 3 | const findIP = (keys: string[], ipToCheck: string) => { 4 | const even = (e: string) => { 5 | return cidrSubnet(e).contains(ipToCheck); 6 | }; 7 | const values = (a: any) => { 8 | return a.some(even); 9 | }; 10 | return keys.some(values); 11 | }; 12 | 13 | export const checkIPs = async ( 14 | meta: hookIPAddress, 15 | ipFromRequest: string, 16 | ): Promise => { 17 | let ipToCheck; 18 | 19 | const keys = Object.values(meta) as string[]; 20 | 21 | if (ipFromRequest === "test-invoke-source-ip") { 22 | ipToCheck = "40.224.81.5"; 23 | } else { 24 | ipToCheck = ipFromRequest; 25 | } 26 | 27 | try { 28 | const response = await findIP(keys, ipToCheck); 29 | return response; 30 | } catch (err) { 31 | console.error("Error within function (findIP)", err); 32 | throw err; 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /functions/githubWebhookIPValidator/src/getGitHubAppJWT.ts: -------------------------------------------------------------------------------- 1 | import { createAppAuth } from "@octokit/auth-app"; 2 | 3 | export const githubAuth = async (): Promise => { 4 | const { 5 | APP_ID: appId, 6 | APP_PRIVATE_KEY: privateKey, 7 | APP_INSTALLATION_ID: appInstallationId, 8 | APP_CLIENT_ID: clientId, 9 | APP_CLIENT_SECRET: clientSecret, 10 | } = process.env; 11 | 12 | const installationId = parseInt(appInstallationId, 10); 13 | 14 | const auth = createAppAuth({ 15 | appId, 16 | privateKey, 17 | installationId, 18 | clientId, 19 | clientSecret, 20 | }); 21 | 22 | try { 23 | const { token } = await auth({ type: "installation" }); 24 | return token; 25 | } catch (err) { 26 | console.error("Error within function (githubAuth)", err); 27 | throw err; 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /functions/githubWebhookIPValidator/src/getIPs.ts: -------------------------------------------------------------------------------- 1 | import { graphql } from "@octokit/graphql"; 2 | 3 | export const getGitHubIpRange = async ( 4 | token: string, 5 | ): Promise => { 6 | const graphqlWithAuth = graphql.defaults({ 7 | headers: { 8 | authorization: `token ${token}`, 9 | }, 10 | }); 11 | 12 | try { 13 | const { meta } = (await graphqlWithAuth( 14 | ` 15 | { 16 | meta { 17 | hookIpAddresses 18 | } 19 | } 20 | `, 21 | )) as IP; 22 | return meta; 23 | } catch (err) { 24 | console.error("Error within function (graphqlWithAuth)", err); 25 | throw err; 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /functions/githubWebhookIPValidator/src/main.ts: -------------------------------------------------------------------------------- 1 | import { APIGatewayProxyEventV2 } from "aws-lambda"; 2 | import { ssm } from "./ssm"; 3 | 4 | import { getGitHubIpRange } from "./getIPs"; 5 | import { githubAuth } from "./getGitHubAppJWT"; 6 | import { checkIPs } from "./checkIPs"; 7 | 8 | export const handler = async ( 9 | event: APIGatewayProxyEventV2, 10 | ): Promise => { 11 | console.log("Event:", event); 12 | console.log("Event Body: ", event.body); 13 | 14 | const sourceIP = event.requestContext.http.sourceIp; 15 | 16 | try { 17 | await ssm(); 18 | const token = (await githubAuth()) as string; 19 | const ips = (await getGitHubIpRange(token)) as hookIPAddress; 20 | const isAuthorized = (await checkIPs(ips, sourceIP)) as boolean; 21 | return { 22 | isAuthorized, 23 | }; 24 | } catch (e) { 25 | console.error(e); 26 | return { 27 | isAuthorized: false, 28 | }; 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /functions/githubWebhookIPValidator/src/ssm.ts: -------------------------------------------------------------------------------- 1 | import { SSMClient, GetParametersByPathCommand } from "@aws-sdk/client-ssm"; 2 | 3 | export const ssm = async (): Promise => { 4 | const region = process.env.REGION ? process.env.REGION : "us-east-1"; 5 | const client = new SSMClient({ region }); 6 | const command = new GetParametersByPathCommand({ 7 | Path: "/codescanning", 8 | WithDecryption: true, 9 | }); 10 | 11 | try { 12 | const { Parameters } = await client.send(command); 13 | 14 | if (Parameters) { 15 | Parameters.forEach((param) => { 16 | const name = param.Name ? param.Name.replace("/codescanning/", "") : ""; 17 | const value = param.Value ? param.Value : ""; 18 | process.env[name] = value; 19 | }); 20 | } 21 | } catch (err) { 22 | console.error("Error within function (ssm)", err); 23 | throw err; 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /functions/githubWebhookIPValidator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node20/tsconfig.json", 3 | "include": ["./src/*.ts"], 4 | "exclude": ["lib", "node_modules"], 5 | "compilerOptions": { 6 | "typeRoots": ["./types", "node_modules/@types"], 7 | "outDir": "./lib", 8 | "noUnusedLocals": true, 9 | "noUnusedParameters": true, 10 | "noImplicitAny": true, 11 | "removeComments": false, 12 | "preserveConstEnums": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /functions/githubWebhookIPValidator/types/common/main.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace NodeJS { 2 | export interface ProcessEnv { 3 | APP_ID: string; 4 | APP_PRIVATE_KEY: string; 5 | APP_INSTALLATION_ID: string; 6 | APP_CLIENT_ID: string; 7 | APP_CLIENT_SECRET: string; 8 | } 9 | } 10 | 11 | interface SimpleResponse { 12 | isAuthorized: boolean; 13 | } 14 | 15 | interface IP { 16 | meta: hookIPAddress; 17 | } 18 | 19 | interface hookIPAddress { 20 | hookIpAddresses: string[]; 21 | } 22 | -------------------------------------------------------------------------------- /functions/githubWebhookIPValidator/types/common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "common", 3 | "version": "1.0.0", 4 | "typings": "main.d.ts" 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codescanningtoslack", 3 | "version": "1.0.0", 4 | "description": "Drop Code Scanning Alerts into Slack", 5 | "main": "main.js", 6 | "scripts": { 7 | "prepare": "husky install", 8 | "prettier:write": "npx prettier --write '**/*.{ts,json,md,yaml,yml}'", 9 | "prettier:check": "npx prettier --check '**/*.{ts,json,md,yaml,yml}'", 10 | "lint:check": "npx eslint '**/*.{ts,json}'", 11 | "lint:write": "npx eslint --fix '**/*.{ts,json}'", 12 | "codespace-setup": ".devcontainer/setup.sh", 13 | "upgrade": ".devcontainer/ncu.sh" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/NickLiffen/codescanningtoslack.git" 18 | }, 19 | "keywords": [ 20 | "GitHub", 21 | "Code", 22 | "Scanning", 23 | "Slack" 24 | ], 25 | "author": "Nick Liffen", 26 | "license": "ISC", 27 | "bugs": { 28 | "url": "https://github.com/NickLiffen/codescanningtoslack/issues" 29 | }, 30 | "homepage": "https://github.com/NickLiffen/codescanningtoslack#readme", 31 | "devDependencies": { 32 | "husky": "^9.0.11", 33 | "@tsconfig/node20": "^20.1.4", 34 | "@types/json-schema": "^7.0.15", 35 | "@types/node": "^20.12.2", 36 | "@typescript-eslint/eslint-plugin": "^7.4.0", 37 | "@typescript-eslint/parser": "^7.4.0", 38 | "eslint": "^8.57.0", 39 | "eslint-config-prettier": "^9.1.0", 40 | "eslint-plugin-prettier": "^5.1.3", 41 | "prettier": "^3.2.5", 42 | "ts-node": "^10.9.2", 43 | "tslib": "^2.6.2", 44 | "typescript": "^5.4.3" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /template.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Transform: "AWS::Serverless-2016-10-31" 3 | 4 | Description: > 5 | Code Scanning Alerts to Slack 6 | 7 | Resources: 8 | AccessLogs: 9 | Type: "AWS::Logs::LogGroup" 10 | 11 | CodeScanningToSlack: 12 | Type: "AWS::Serverless::Function" 13 | Properties: 14 | CodeUri: functions/codeScanningToSlack 15 | Description: "A Lambda that connects GitHub Code Scanning Results to a Slack Channel" 16 | Environment: 17 | Variables: 18 | NODE_ENV: Production 19 | REGION: !Ref "AWS::Region" 20 | Events: 21 | GatewayEndpoint: 22 | Properties: 23 | ApiId: !Ref HttpApi 24 | Method: POST 25 | Path: /codescanning 26 | Type: HttpApi 27 | Handler: lib/main.handler 28 | Policies: 29 | - AmazonSSMReadOnlyAccess 30 | Runtime: nodejs20.x 31 | Timeout: 60 32 | Tracing: Active 33 | 34 | GitHubWebhookIPValidator: 35 | Type: "AWS::Serverless::Function" 36 | Properties: 37 | CodeUri: functions/githubWebhookIPValidator 38 | Description: "A Lambda Function that validates the IP comes from GitHub" 39 | Environment: 40 | Variables: 41 | NODE_ENV: Production 42 | REGION: !Ref "AWS::Region" 43 | Handler: lib/main.handler 44 | Policies: 45 | - AmazonSSMReadOnlyAccess 46 | Runtime: nodejs20.x 47 | Timeout: 60 48 | Tracing: Active 49 | 50 | HttpApi: 51 | Type: "AWS::Serverless::HttpApi" 52 | Properties: 53 | AccessLogSettings: 54 | DestinationArn: !GetAtt AccessLogs.Arn 55 | Format: >- 56 | { "requestId":"$context.requestId","ip": "$context.identity.sourceIp", 57 | "requestTime":"$context.requestTime","httpMethod":"$context.httpMethod", 58 | "routeKey":"$context.routeKey","status":"$context.status", 59 | "protocol":"$context.protocol","responseLength":"$context.responseLength", 60 | "error" : $context.authorizer.error } 61 | Auth: 62 | Authorizers: 63 | LambdaAuthorizer: 64 | AuthorizerPayloadFormatVersion: "2.0" 65 | EnableSimpleResponses: true 66 | FunctionArn: !GetAtt GitHubWebhookIPValidator.Arn 67 | FunctionInvokeRole: !GetAtt LambdaInvokeRole.Arn 68 | Identity: 69 | Headers: 70 | - X-Hub-Signature 71 | DefaultAuthorizer: LambdaAuthorizer 72 | RouteSettings: 73 | "POST /codescanning": 74 | ThrottlingBurstLimit: 10 75 | 76 | LambdaInvokeRole: 77 | Type: "AWS::IAM::Role" 78 | Properties: 79 | AssumeRolePolicyDocument: 80 | Version: "2012-10-17" 81 | Statement: 82 | - Effect: Allow 83 | Principal: 84 | Service: 85 | - apigateway.amazonaws.com 86 | Action: 87 | - "sts:AssumeRole" 88 | 89 | LambdaInvokePolicy: 90 | Type: "AWS::IAM::Policy" 91 | Properties: 92 | PolicyName: "LambdaInvokePolicy" 93 | PolicyDocument: 94 | Version: "2012-10-17" 95 | Statement: 96 | - Effect: "Allow" 97 | Action: "lambda:InvokeFunction" 98 | Resource: !GetAtt GitHubWebhookIPValidator.Arn 99 | Roles: 100 | - Ref: "LambdaInvokeRole" 101 | 102 | Outputs: 103 | HttpApiUrl: 104 | Description: HTTP API URL 105 | Value: 106 | Fn::Sub: "https://${HttpApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/" 107 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@aashutoshrathi/word-wrap@^1.2.3": 6 | version "1.2.6" 7 | resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" 8 | integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== 9 | 10 | "@cspotcode/source-map-support@^0.8.0": 11 | version "0.8.1" 12 | resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" 13 | integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== 14 | dependencies: 15 | "@jridgewell/trace-mapping" "0.3.9" 16 | 17 | "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": 18 | version "4.4.0" 19 | resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" 20 | integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== 21 | dependencies: 22 | eslint-visitor-keys "^3.3.0" 23 | 24 | "@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": 25 | version "4.6.2" 26 | resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.6.2.tgz#1816b5f6948029c5eaacb0703b850ee0cb37d8f8" 27 | integrity sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw== 28 | 29 | "@eslint/eslintrc@^2.1.4": 30 | version "2.1.4" 31 | resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" 32 | integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== 33 | dependencies: 34 | ajv "^6.12.4" 35 | debug "^4.3.2" 36 | espree "^9.6.0" 37 | globals "^13.19.0" 38 | ignore "^5.2.0" 39 | import-fresh "^3.2.1" 40 | js-yaml "^4.1.0" 41 | minimatch "^3.1.2" 42 | strip-json-comments "^3.1.1" 43 | 44 | "@eslint/js@8.57.0": 45 | version "8.57.0" 46 | resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" 47 | integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== 48 | 49 | "@humanwhocodes/config-array@^0.11.14": 50 | version "0.11.14" 51 | resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" 52 | integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== 53 | dependencies: 54 | "@humanwhocodes/object-schema" "^2.0.2" 55 | debug "^4.3.1" 56 | minimatch "^3.0.5" 57 | 58 | "@humanwhocodes/module-importer@^1.0.1": 59 | version "1.0.1" 60 | resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" 61 | integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== 62 | 63 | "@humanwhocodes/object-schema@^2.0.2": 64 | version "2.0.2" 65 | resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" 66 | integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== 67 | 68 | "@jridgewell/resolve-uri@^3.0.3": 69 | version "3.0.8" 70 | resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz#687cc2bbf243f4e9a868ecf2262318e2658873a1" 71 | integrity sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w== 72 | 73 | "@jridgewell/sourcemap-codec@^1.4.10": 74 | version "1.4.14" 75 | resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" 76 | integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== 77 | 78 | "@jridgewell/trace-mapping@0.3.9": 79 | version "0.3.9" 80 | resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" 81 | integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== 82 | dependencies: 83 | "@jridgewell/resolve-uri" "^3.0.3" 84 | "@jridgewell/sourcemap-codec" "^1.4.10" 85 | 86 | "@nodelib/fs.scandir@2.1.5": 87 | version "2.1.5" 88 | resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" 89 | integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== 90 | dependencies: 91 | "@nodelib/fs.stat" "2.0.5" 92 | run-parallel "^1.1.9" 93 | 94 | "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": 95 | version "2.0.5" 96 | resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" 97 | integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== 98 | 99 | "@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": 100 | version "1.2.8" 101 | resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" 102 | integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== 103 | dependencies: 104 | "@nodelib/fs.scandir" "2.1.5" 105 | fastq "^1.6.0" 106 | 107 | "@pkgr/core@^0.1.0": 108 | version "0.1.0" 109 | resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.0.tgz#7d8dacb7fdef0e4387caf7396cbd77f179867d06" 110 | integrity sha512-Zwq5OCzuwJC2jwqmpEQt7Ds1DTi6BWSwoGkbb1n9pO3hzb35BoJELx7c0T23iDkBGkh2e7tvOtjF3tr3OaQHDQ== 111 | 112 | "@tsconfig/node10@^1.0.7": 113 | version "1.0.8" 114 | resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" 115 | integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== 116 | 117 | "@tsconfig/node12@^1.0.7": 118 | version "1.0.9" 119 | resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" 120 | integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== 121 | 122 | "@tsconfig/node14@^1.0.0": 123 | version "1.0.1" 124 | resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" 125 | integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== 126 | 127 | "@tsconfig/node16@^1.0.2": 128 | version "1.0.2" 129 | resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" 130 | integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== 131 | 132 | "@tsconfig/node20@^20.1.4": 133 | version "20.1.4" 134 | resolved "https://registry.yarnpkg.com/@tsconfig/node20/-/node20-20.1.4.tgz#3457d42eddf12d3bde3976186ab0cd22b85df928" 135 | integrity sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg== 136 | 137 | "@types/json-schema@^7.0.12", "@types/json-schema@^7.0.15": 138 | version "7.0.15" 139 | resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" 140 | integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== 141 | 142 | "@types/node@^20.12.2": 143 | version "20.12.2" 144 | resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.2.tgz#9facdd11102f38b21b4ebedd9d7999663343d72e" 145 | integrity sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ== 146 | dependencies: 147 | undici-types "~5.26.4" 148 | 149 | "@types/semver@^7.5.0": 150 | version "7.5.0" 151 | resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" 152 | integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== 153 | 154 | "@typescript-eslint/eslint-plugin@^7.4.0": 155 | version "7.4.0" 156 | resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.4.0.tgz#de61c3083842fc6ac889d2fc83c9a96b55ab8328" 157 | integrity sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw== 158 | dependencies: 159 | "@eslint-community/regexpp" "^4.5.1" 160 | "@typescript-eslint/scope-manager" "7.4.0" 161 | "@typescript-eslint/type-utils" "7.4.0" 162 | "@typescript-eslint/utils" "7.4.0" 163 | "@typescript-eslint/visitor-keys" "7.4.0" 164 | debug "^4.3.4" 165 | graphemer "^1.4.0" 166 | ignore "^5.2.4" 167 | natural-compare "^1.4.0" 168 | semver "^7.5.4" 169 | ts-api-utils "^1.0.1" 170 | 171 | "@typescript-eslint/parser@^7.4.0": 172 | version "7.4.0" 173 | resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.4.0.tgz#540f4321de1e52b886c0fa68628af1459954c1f1" 174 | integrity sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ== 175 | dependencies: 176 | "@typescript-eslint/scope-manager" "7.4.0" 177 | "@typescript-eslint/types" "7.4.0" 178 | "@typescript-eslint/typescript-estree" "7.4.0" 179 | "@typescript-eslint/visitor-keys" "7.4.0" 180 | debug "^4.3.4" 181 | 182 | "@typescript-eslint/scope-manager@7.4.0": 183 | version "7.4.0" 184 | resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.4.0.tgz#acfc69261f10ece7bf7ece1734f1713392c3655f" 185 | integrity sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw== 186 | dependencies: 187 | "@typescript-eslint/types" "7.4.0" 188 | "@typescript-eslint/visitor-keys" "7.4.0" 189 | 190 | "@typescript-eslint/type-utils@7.4.0": 191 | version "7.4.0" 192 | resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.4.0.tgz#cfcaab21bcca441c57da5d3a1153555e39028cbd" 193 | integrity sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw== 194 | dependencies: 195 | "@typescript-eslint/typescript-estree" "7.4.0" 196 | "@typescript-eslint/utils" "7.4.0" 197 | debug "^4.3.4" 198 | ts-api-utils "^1.0.1" 199 | 200 | "@typescript-eslint/types@7.4.0": 201 | version "7.4.0" 202 | resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.4.0.tgz#ee9dafa75c99eaee49de6dcc9348b45d354419b6" 203 | integrity sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw== 204 | 205 | "@typescript-eslint/typescript-estree@7.4.0": 206 | version "7.4.0" 207 | resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.4.0.tgz#12dbcb4624d952f72c10a9f4431284fca24624f4" 208 | integrity sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg== 209 | dependencies: 210 | "@typescript-eslint/types" "7.4.0" 211 | "@typescript-eslint/visitor-keys" "7.4.0" 212 | debug "^4.3.4" 213 | globby "^11.1.0" 214 | is-glob "^4.0.3" 215 | minimatch "9.0.3" 216 | semver "^7.5.4" 217 | ts-api-utils "^1.0.1" 218 | 219 | "@typescript-eslint/utils@7.4.0": 220 | version "7.4.0" 221 | resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.4.0.tgz#d889a0630cab88bddedaf7c845c64a00576257bd" 222 | integrity sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg== 223 | dependencies: 224 | "@eslint-community/eslint-utils" "^4.4.0" 225 | "@types/json-schema" "^7.0.12" 226 | "@types/semver" "^7.5.0" 227 | "@typescript-eslint/scope-manager" "7.4.0" 228 | "@typescript-eslint/types" "7.4.0" 229 | "@typescript-eslint/typescript-estree" "7.4.0" 230 | semver "^7.5.4" 231 | 232 | "@typescript-eslint/visitor-keys@7.4.0": 233 | version "7.4.0" 234 | resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.4.0.tgz#0c8ff2c1f8a6fe8d7d1a57ebbd4a638e86a60a94" 235 | integrity sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA== 236 | dependencies: 237 | "@typescript-eslint/types" "7.4.0" 238 | eslint-visitor-keys "^3.4.1" 239 | 240 | "@ungap/structured-clone@^1.2.0": 241 | version "1.2.0" 242 | resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" 243 | integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== 244 | 245 | acorn-jsx@^5.3.2: 246 | version "5.3.2" 247 | resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" 248 | integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== 249 | 250 | acorn-walk@^8.1.1: 251 | version "8.2.0" 252 | resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" 253 | integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== 254 | 255 | acorn@^8.4.1, acorn@^8.9.0: 256 | version "8.9.0" 257 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.9.0.tgz#78a16e3b2bcc198c10822786fa6679e245db5b59" 258 | integrity sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ== 259 | 260 | ajv@^6.12.4: 261 | version "6.12.6" 262 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" 263 | integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== 264 | dependencies: 265 | fast-deep-equal "^3.1.1" 266 | fast-json-stable-stringify "^2.0.0" 267 | json-schema-traverse "^0.4.1" 268 | uri-js "^4.2.2" 269 | 270 | ansi-regex@^5.0.1: 271 | version "5.0.1" 272 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" 273 | integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== 274 | 275 | ansi-styles@^4.1.0: 276 | version "4.3.0" 277 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" 278 | integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== 279 | dependencies: 280 | color-convert "^2.0.1" 281 | 282 | arg@^4.1.0: 283 | version "4.1.3" 284 | resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" 285 | integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== 286 | 287 | argparse@^2.0.1: 288 | version "2.0.1" 289 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" 290 | integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== 291 | 292 | array-union@^2.1.0: 293 | version "2.1.0" 294 | resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" 295 | integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== 296 | 297 | balanced-match@^1.0.0: 298 | version "1.0.2" 299 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 300 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 301 | 302 | brace-expansion@^1.1.7: 303 | version "1.1.11" 304 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 305 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 306 | dependencies: 307 | balanced-match "^1.0.0" 308 | concat-map "0.0.1" 309 | 310 | brace-expansion@^2.0.1: 311 | version "2.0.1" 312 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" 313 | integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== 314 | dependencies: 315 | balanced-match "^1.0.0" 316 | 317 | braces@^3.0.2: 318 | version "3.0.2" 319 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 320 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 321 | dependencies: 322 | fill-range "^7.0.1" 323 | 324 | callsites@^3.0.0: 325 | version "3.1.0" 326 | resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" 327 | integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== 328 | 329 | chalk@^4.0.0: 330 | version "4.1.2" 331 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" 332 | integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== 333 | dependencies: 334 | ansi-styles "^4.1.0" 335 | supports-color "^7.1.0" 336 | 337 | color-convert@^2.0.1: 338 | version "2.0.1" 339 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 340 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 341 | dependencies: 342 | color-name "~1.1.4" 343 | 344 | color-name@~1.1.4: 345 | version "1.1.4" 346 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 347 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 348 | 349 | concat-map@0.0.1: 350 | version "0.0.1" 351 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 352 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 353 | 354 | create-require@^1.1.0: 355 | version "1.1.1" 356 | resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" 357 | integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== 358 | 359 | cross-spawn@^7.0.2: 360 | version "7.0.3" 361 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" 362 | integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== 363 | dependencies: 364 | path-key "^3.1.0" 365 | shebang-command "^2.0.0" 366 | which "^2.0.1" 367 | 368 | debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: 369 | version "4.3.4" 370 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" 371 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== 372 | dependencies: 373 | ms "2.1.2" 374 | 375 | deep-is@^0.1.3: 376 | version "0.1.4" 377 | resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" 378 | integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== 379 | 380 | diff@^4.0.1: 381 | version "4.0.2" 382 | resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" 383 | integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== 384 | 385 | dir-glob@^3.0.1: 386 | version "3.0.1" 387 | resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" 388 | integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== 389 | dependencies: 390 | path-type "^4.0.0" 391 | 392 | doctrine@^3.0.0: 393 | version "3.0.0" 394 | resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" 395 | integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== 396 | dependencies: 397 | esutils "^2.0.2" 398 | 399 | escape-string-regexp@^4.0.0: 400 | version "4.0.0" 401 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" 402 | integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== 403 | 404 | eslint-config-prettier@^9.1.0: 405 | version "9.1.0" 406 | resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz#31af3d94578645966c082fcb71a5846d3c94867f" 407 | integrity sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw== 408 | 409 | eslint-plugin-prettier@^5.1.3: 410 | version "5.1.3" 411 | resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz#17cfade9e732cef32b5f5be53bd4e07afd8e67e1" 412 | integrity sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw== 413 | dependencies: 414 | prettier-linter-helpers "^1.0.0" 415 | synckit "^0.8.6" 416 | 417 | eslint-scope@^7.2.2: 418 | version "7.2.2" 419 | resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" 420 | integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== 421 | dependencies: 422 | esrecurse "^4.3.0" 423 | estraverse "^5.2.0" 424 | 425 | eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: 426 | version "3.4.3" 427 | resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" 428 | integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== 429 | 430 | eslint@^8.57.0: 431 | version "8.57.0" 432 | resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" 433 | integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== 434 | dependencies: 435 | "@eslint-community/eslint-utils" "^4.2.0" 436 | "@eslint-community/regexpp" "^4.6.1" 437 | "@eslint/eslintrc" "^2.1.4" 438 | "@eslint/js" "8.57.0" 439 | "@humanwhocodes/config-array" "^0.11.14" 440 | "@humanwhocodes/module-importer" "^1.0.1" 441 | "@nodelib/fs.walk" "^1.2.8" 442 | "@ungap/structured-clone" "^1.2.0" 443 | ajv "^6.12.4" 444 | chalk "^4.0.0" 445 | cross-spawn "^7.0.2" 446 | debug "^4.3.2" 447 | doctrine "^3.0.0" 448 | escape-string-regexp "^4.0.0" 449 | eslint-scope "^7.2.2" 450 | eslint-visitor-keys "^3.4.3" 451 | espree "^9.6.1" 452 | esquery "^1.4.2" 453 | esutils "^2.0.2" 454 | fast-deep-equal "^3.1.3" 455 | file-entry-cache "^6.0.1" 456 | find-up "^5.0.0" 457 | glob-parent "^6.0.2" 458 | globals "^13.19.0" 459 | graphemer "^1.4.0" 460 | ignore "^5.2.0" 461 | imurmurhash "^0.1.4" 462 | is-glob "^4.0.0" 463 | is-path-inside "^3.0.3" 464 | js-yaml "^4.1.0" 465 | json-stable-stringify-without-jsonify "^1.0.1" 466 | levn "^0.4.1" 467 | lodash.merge "^4.6.2" 468 | minimatch "^3.1.2" 469 | natural-compare "^1.4.0" 470 | optionator "^0.9.3" 471 | strip-ansi "^6.0.1" 472 | text-table "^0.2.0" 473 | 474 | espree@^9.6.0, espree@^9.6.1: 475 | version "9.6.1" 476 | resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" 477 | integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== 478 | dependencies: 479 | acorn "^8.9.0" 480 | acorn-jsx "^5.3.2" 481 | eslint-visitor-keys "^3.4.1" 482 | 483 | esquery@^1.4.2: 484 | version "1.5.0" 485 | resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" 486 | integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== 487 | dependencies: 488 | estraverse "^5.1.0" 489 | 490 | esrecurse@^4.3.0: 491 | version "4.3.0" 492 | resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" 493 | integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== 494 | dependencies: 495 | estraverse "^5.2.0" 496 | 497 | estraverse@^5.1.0, estraverse@^5.2.0: 498 | version "5.3.0" 499 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" 500 | integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== 501 | 502 | esutils@^2.0.2: 503 | version "2.0.3" 504 | resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" 505 | integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== 506 | 507 | fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: 508 | version "3.1.3" 509 | resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" 510 | integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== 511 | 512 | fast-diff@^1.1.2: 513 | version "1.2.0" 514 | resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" 515 | integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== 516 | 517 | fast-glob@^3.2.9: 518 | version "3.2.11" 519 | resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" 520 | integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== 521 | dependencies: 522 | "@nodelib/fs.stat" "^2.0.2" 523 | "@nodelib/fs.walk" "^1.2.3" 524 | glob-parent "^5.1.2" 525 | merge2 "^1.3.0" 526 | micromatch "^4.0.4" 527 | 528 | fast-json-stable-stringify@^2.0.0: 529 | version "2.1.0" 530 | resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" 531 | integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== 532 | 533 | fast-levenshtein@^2.0.6: 534 | version "2.0.6" 535 | resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" 536 | integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= 537 | 538 | fastq@^1.6.0: 539 | version "1.13.0" 540 | resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" 541 | integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== 542 | dependencies: 543 | reusify "^1.0.4" 544 | 545 | file-entry-cache@^6.0.1: 546 | version "6.0.1" 547 | resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" 548 | integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== 549 | dependencies: 550 | flat-cache "^3.0.4" 551 | 552 | fill-range@^7.0.1: 553 | version "7.0.1" 554 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 555 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 556 | dependencies: 557 | to-regex-range "^5.0.1" 558 | 559 | find-up@^5.0.0: 560 | version "5.0.0" 561 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" 562 | integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== 563 | dependencies: 564 | locate-path "^6.0.0" 565 | path-exists "^4.0.0" 566 | 567 | flat-cache@^3.0.4: 568 | version "3.0.4" 569 | resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" 570 | integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== 571 | dependencies: 572 | flatted "^3.1.0" 573 | rimraf "^3.0.2" 574 | 575 | flatted@^3.1.0: 576 | version "3.2.5" 577 | resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" 578 | integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== 579 | 580 | fs.realpath@^1.0.0: 581 | version "1.0.0" 582 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 583 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 584 | 585 | glob-parent@^5.1.2: 586 | version "5.1.2" 587 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 588 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 589 | dependencies: 590 | is-glob "^4.0.1" 591 | 592 | glob-parent@^6.0.2: 593 | version "6.0.2" 594 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" 595 | integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== 596 | dependencies: 597 | is-glob "^4.0.3" 598 | 599 | glob@^7.1.3: 600 | version "7.2.0" 601 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" 602 | integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== 603 | dependencies: 604 | fs.realpath "^1.0.0" 605 | inflight "^1.0.4" 606 | inherits "2" 607 | minimatch "^3.0.4" 608 | once "^1.3.0" 609 | path-is-absolute "^1.0.0" 610 | 611 | globals@^13.19.0: 612 | version "13.20.0" 613 | resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" 614 | integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== 615 | dependencies: 616 | type-fest "^0.20.2" 617 | 618 | globby@^11.1.0: 619 | version "11.1.0" 620 | resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" 621 | integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== 622 | dependencies: 623 | array-union "^2.1.0" 624 | dir-glob "^3.0.1" 625 | fast-glob "^3.2.9" 626 | ignore "^5.2.0" 627 | merge2 "^1.4.1" 628 | slash "^3.0.0" 629 | 630 | graphemer@^1.4.0: 631 | version "1.4.0" 632 | resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" 633 | integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== 634 | 635 | has-flag@^4.0.0: 636 | version "4.0.0" 637 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" 638 | integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== 639 | 640 | husky@^9.0.11: 641 | version "9.0.11" 642 | resolved "https://registry.yarnpkg.com/husky/-/husky-9.0.11.tgz#fc91df4c756050de41b3e478b2158b87c1e79af9" 643 | integrity sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw== 644 | 645 | ignore@^5.2.0, ignore@^5.2.4: 646 | version "5.2.4" 647 | resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" 648 | integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== 649 | 650 | import-fresh@^3.2.1: 651 | version "3.3.0" 652 | resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" 653 | integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== 654 | dependencies: 655 | parent-module "^1.0.0" 656 | resolve-from "^4.0.0" 657 | 658 | imurmurhash@^0.1.4: 659 | version "0.1.4" 660 | resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" 661 | integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= 662 | 663 | inflight@^1.0.4: 664 | version "1.0.6" 665 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 666 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 667 | dependencies: 668 | once "^1.3.0" 669 | wrappy "1" 670 | 671 | inherits@2: 672 | version "2.0.4" 673 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 674 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 675 | 676 | is-extglob@^2.1.1: 677 | version "2.1.1" 678 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 679 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= 680 | 681 | is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: 682 | version "4.0.3" 683 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" 684 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 685 | dependencies: 686 | is-extglob "^2.1.1" 687 | 688 | is-number@^7.0.0: 689 | version "7.0.0" 690 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 691 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 692 | 693 | is-path-inside@^3.0.3: 694 | version "3.0.3" 695 | resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" 696 | integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== 697 | 698 | isexe@^2.0.0: 699 | version "2.0.0" 700 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 701 | integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= 702 | 703 | js-yaml@^4.1.0: 704 | version "4.1.0" 705 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" 706 | integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== 707 | dependencies: 708 | argparse "^2.0.1" 709 | 710 | json-schema-traverse@^0.4.1: 711 | version "0.4.1" 712 | resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" 713 | integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== 714 | 715 | json-stable-stringify-without-jsonify@^1.0.1: 716 | version "1.0.1" 717 | resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" 718 | integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= 719 | 720 | levn@^0.4.1: 721 | version "0.4.1" 722 | resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" 723 | integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== 724 | dependencies: 725 | prelude-ls "^1.2.1" 726 | type-check "~0.4.0" 727 | 728 | locate-path@^6.0.0: 729 | version "6.0.0" 730 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" 731 | integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== 732 | dependencies: 733 | p-locate "^5.0.0" 734 | 735 | lodash.merge@^4.6.2: 736 | version "4.6.2" 737 | resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" 738 | integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== 739 | 740 | lru-cache@^6.0.0: 741 | version "6.0.0" 742 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" 743 | integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== 744 | dependencies: 745 | yallist "^4.0.0" 746 | 747 | make-error@^1.1.1: 748 | version "1.3.6" 749 | resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" 750 | integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== 751 | 752 | merge2@^1.3.0, merge2@^1.4.1: 753 | version "1.4.1" 754 | resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" 755 | integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== 756 | 757 | micromatch@^4.0.4: 758 | version "4.0.5" 759 | resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" 760 | integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== 761 | dependencies: 762 | braces "^3.0.2" 763 | picomatch "^2.3.1" 764 | 765 | minimatch@9.0.3: 766 | version "9.0.3" 767 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" 768 | integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== 769 | dependencies: 770 | brace-expansion "^2.0.1" 771 | 772 | minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.2: 773 | version "3.1.2" 774 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" 775 | integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== 776 | dependencies: 777 | brace-expansion "^1.1.7" 778 | 779 | ms@2.1.2: 780 | version "2.1.2" 781 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 782 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 783 | 784 | natural-compare@^1.4.0: 785 | version "1.4.0" 786 | resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" 787 | integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= 788 | 789 | once@^1.3.0: 790 | version "1.4.0" 791 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 792 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 793 | dependencies: 794 | wrappy "1" 795 | 796 | optionator@^0.9.3: 797 | version "0.9.3" 798 | resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" 799 | integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== 800 | dependencies: 801 | "@aashutoshrathi/word-wrap" "^1.2.3" 802 | deep-is "^0.1.3" 803 | fast-levenshtein "^2.0.6" 804 | levn "^0.4.1" 805 | prelude-ls "^1.2.1" 806 | type-check "^0.4.0" 807 | 808 | p-limit@^3.0.2: 809 | version "3.1.0" 810 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" 811 | integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== 812 | dependencies: 813 | yocto-queue "^0.1.0" 814 | 815 | p-locate@^5.0.0: 816 | version "5.0.0" 817 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" 818 | integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== 819 | dependencies: 820 | p-limit "^3.0.2" 821 | 822 | parent-module@^1.0.0: 823 | version "1.0.1" 824 | resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" 825 | integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== 826 | dependencies: 827 | callsites "^3.0.0" 828 | 829 | path-exists@^4.0.0: 830 | version "4.0.0" 831 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" 832 | integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== 833 | 834 | path-is-absolute@^1.0.0: 835 | version "1.0.1" 836 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 837 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 838 | 839 | path-key@^3.1.0: 840 | version "3.1.1" 841 | resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" 842 | integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== 843 | 844 | path-type@^4.0.0: 845 | version "4.0.0" 846 | resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" 847 | integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== 848 | 849 | picomatch@^2.3.1: 850 | version "2.3.1" 851 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 852 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 853 | 854 | prelude-ls@^1.2.1: 855 | version "1.2.1" 856 | resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" 857 | integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== 858 | 859 | prettier-linter-helpers@^1.0.0: 860 | version "1.0.0" 861 | resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" 862 | integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== 863 | dependencies: 864 | fast-diff "^1.1.2" 865 | 866 | prettier@^3.2.5: 867 | version "3.2.5" 868 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368" 869 | integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== 870 | 871 | punycode@^2.1.0: 872 | version "2.1.1" 873 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" 874 | integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== 875 | 876 | queue-microtask@^1.2.2: 877 | version "1.2.3" 878 | resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" 879 | integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== 880 | 881 | resolve-from@^4.0.0: 882 | version "4.0.0" 883 | resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" 884 | integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== 885 | 886 | reusify@^1.0.4: 887 | version "1.0.4" 888 | resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" 889 | integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== 890 | 891 | rimraf@^3.0.2: 892 | version "3.0.2" 893 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" 894 | integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== 895 | dependencies: 896 | glob "^7.1.3" 897 | 898 | run-parallel@^1.1.9: 899 | version "1.2.0" 900 | resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" 901 | integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== 902 | dependencies: 903 | queue-microtask "^1.2.2" 904 | 905 | semver@^7.5.4: 906 | version "7.5.4" 907 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" 908 | integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== 909 | dependencies: 910 | lru-cache "^6.0.0" 911 | 912 | shebang-command@^2.0.0: 913 | version "2.0.0" 914 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" 915 | integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== 916 | dependencies: 917 | shebang-regex "^3.0.0" 918 | 919 | shebang-regex@^3.0.0: 920 | version "3.0.0" 921 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" 922 | integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== 923 | 924 | slash@^3.0.0: 925 | version "3.0.0" 926 | resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" 927 | integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== 928 | 929 | strip-ansi@^6.0.1: 930 | version "6.0.1" 931 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" 932 | integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== 933 | dependencies: 934 | ansi-regex "^5.0.1" 935 | 936 | strip-json-comments@^3.1.1: 937 | version "3.1.1" 938 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" 939 | integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== 940 | 941 | supports-color@^7.1.0: 942 | version "7.2.0" 943 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" 944 | integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== 945 | dependencies: 946 | has-flag "^4.0.0" 947 | 948 | synckit@^0.8.6: 949 | version "0.8.8" 950 | resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.8.tgz#fe7fe446518e3d3d49f5e429f443cf08b6edfcd7" 951 | integrity sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ== 952 | dependencies: 953 | "@pkgr/core" "^0.1.0" 954 | tslib "^2.6.2" 955 | 956 | text-table@^0.2.0: 957 | version "0.2.0" 958 | resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" 959 | integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= 960 | 961 | to-regex-range@^5.0.1: 962 | version "5.0.1" 963 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 964 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 965 | dependencies: 966 | is-number "^7.0.0" 967 | 968 | ts-api-utils@^1.0.1: 969 | version "1.0.1" 970 | resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.1.tgz#8144e811d44c749cd65b2da305a032510774452d" 971 | integrity sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A== 972 | 973 | ts-node@^10.9.2: 974 | version "10.9.2" 975 | resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" 976 | integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== 977 | dependencies: 978 | "@cspotcode/source-map-support" "^0.8.0" 979 | "@tsconfig/node10" "^1.0.7" 980 | "@tsconfig/node12" "^1.0.7" 981 | "@tsconfig/node14" "^1.0.0" 982 | "@tsconfig/node16" "^1.0.2" 983 | acorn "^8.4.1" 984 | acorn-walk "^8.1.1" 985 | arg "^4.1.0" 986 | create-require "^1.1.0" 987 | diff "^4.0.1" 988 | make-error "^1.1.1" 989 | v8-compile-cache-lib "^3.0.1" 990 | yn "3.1.1" 991 | 992 | tslib@^2.6.2: 993 | version "2.6.2" 994 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" 995 | integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== 996 | 997 | type-check@^0.4.0, type-check@~0.4.0: 998 | version "0.4.0" 999 | resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" 1000 | integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== 1001 | dependencies: 1002 | prelude-ls "^1.2.1" 1003 | 1004 | type-fest@^0.20.2: 1005 | version "0.20.2" 1006 | resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" 1007 | integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== 1008 | 1009 | typescript@^5.4.3: 1010 | version "5.4.3" 1011 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.3.tgz#5c6fedd4c87bee01cd7a528a30145521f8e0feff" 1012 | integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== 1013 | 1014 | undici-types@~5.26.4: 1015 | version "5.26.5" 1016 | resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" 1017 | integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== 1018 | 1019 | uri-js@^4.2.2: 1020 | version "4.4.1" 1021 | resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" 1022 | integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== 1023 | dependencies: 1024 | punycode "^2.1.0" 1025 | 1026 | v8-compile-cache-lib@^3.0.1: 1027 | version "3.0.1" 1028 | resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" 1029 | integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== 1030 | 1031 | which@^2.0.1: 1032 | version "2.0.2" 1033 | resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" 1034 | integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== 1035 | dependencies: 1036 | isexe "^2.0.0" 1037 | 1038 | wrappy@1: 1039 | version "1.0.2" 1040 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 1041 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 1042 | 1043 | yallist@^4.0.0: 1044 | version "4.0.0" 1045 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" 1046 | integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== 1047 | 1048 | yn@3.1.1: 1049 | version "3.1.1" 1050 | resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" 1051 | integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== 1052 | 1053 | yocto-queue@^0.1.0: 1054 | version "0.1.0" 1055 | resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" 1056 | integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== 1057 | --------------------------------------------------------------------------------