diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..74822c8e --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,34 @@ +module.exports = { + env: { + node: true, + es2021: true, + 'jest/globals': true, + commonjs: true + }, + extends: [ + 'eslint:recommended' + ], + globals: { + Atomics: 'readonly', + SharedArrayBuffer: 'readonly' + }, + plugins: [ + 'jest' + ], + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module' + }, + rules: { + 'indent': ['error', 2], + 'linebreak-style': ['error', 'unix'], + 'quotes': ['error', 'single', { 'allowTemplateLiterals': true }], + 'semi': ['error', 'always'], + 'no-unused-vars': ['warn', { 'argsIgnorePattern': '^_' }], + 'jest/no-disabled-tests': 'warn', + 'jest/no-focused-tests': 'error', + 'jest/no-identical-title': 'error', + 'jest/prefer-to-have-length': 'warn', + 'jest/valid-expect': 'error' + } +}; diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..da057da3 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,6 @@ +*Issue #, if available:* + +*Description of changes:* + + +By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..38303034 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: +- package-ecosystem: npm + directory: "/" + schedule: + interval: weekly + day: tuesday + open-pull-requests-limit: 10 \ No newline at end of file diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 00000000..24a07042 --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,32 @@ +on: + [pull_request] + +name: Check + +jobs: + check: + name: Run Unit Tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Run tests + run: | + npm ci + npm test + + conventional-commits: + name: Semantic Pull Request + runs-on: ubuntu-latest + steps: + - name: validate + uses: actions/github-script@v7 + with: + script: | + // See https://gist.github.com/marcojahn/482410b728c31b221b70ea6d2c433f0c#file-conventional-commit-regex-md + const regex = /^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test){1}(\([\w\-\.]+\))?(!)?: ([\w ])+([\s\S]*)/g; + const pr = context.payload.pull_request; + const title = pr.title; + if (title.match(regex) == null) { + throw `PR title "${title}"" does not match conventional commits from https://www.conventionalcommits.org/en/v1.0.0/` + } diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..7c44b2c8 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,66 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +name: "CodeQL" + +on: + push: + branches: [ main ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ main ] + schedule: + - cron: '0 12 * * 1' # Runs at 12:00 UTC on Mondays + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + # Override automatic language detection by specifying the list of languages + language: [ 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/dependabot-autoapprove.yml b/.github/workflows/dependabot-autoapprove.yml new file mode 100644 index 00000000..0d1268d2 --- /dev/null +++ b/.github/workflows/dependabot-autoapprove.yml @@ -0,0 +1,27 @@ +name: Dependabot auto-approve + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + auto-approve: + name: Auto-approve Dependabot PRs + runs-on: ubuntu-latest + if: ${{ github.actor == 'dependabot[bot]' }} + permissions: + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Approve PR if not already approved + run: | + gh pr checkout "${{ github.event.pull_request.number }}" + if [ "$(gh pr status --json reviewDecision -q .currentBranch.reviewDecision)" != "APPROVED" ]; then + gh pr review "${{ github.event.pull_request.number }}" --approve + else + echo "PR already approved" + fi + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml new file mode 100644 index 00000000..4cf5685a --- /dev/null +++ b/.github/workflows/package.yml @@ -0,0 +1,36 @@ +name: Package + +on: + pull_request: + +jobs: + build: + name: Package distribution file + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Init a git repo + uses: actions/checkout@v4 + - name: Checkout PR + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh pr checkout ${{ github.event.pull_request.number }} + - name: Package + run: | + npm ci + npm test + npm run build + - name: Commit to PR + if: github.actor == 'dependabot[bot]' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + git config --global user.name "GitHub Actions" + git add dist/ + git commit -m "chore: Update dist" || echo "No changes to commit" + git push + - name: Check git diff + if: github.actor != 'dependabot[bot]' + run: | + git diff --exit-code dist/index.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..be72404b --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules +dist +.DS_Store +coverage +repolinter +deploy-lambda.yml +Config \ No newline at end of file diff --git a/.mergify.yml b/.mergify.yml new file mode 100644 index 00000000..270d33f5 --- /dev/null +++ b/.mergify.yml @@ -0,0 +1,43 @@ +queue_rules: + - name: default + merge_conditions: + - status-success=Run Unit Tests + - status-success=Semantic Pull Request + - status-success=Analyze (javascript) + merge_method: squash + +pull_request_rules: + - name: Automatically merge on CI success and review approval + conditions: + - base~=master|integ-tests + - "#approved-reviews-by>=1" + - approved-reviews-by= # @your-org/your-team-name + - -approved-reviews-by~=author + - status-success=Run Unit Tests + - status-success=Semantic Pull Request + - status-success=Analyze (javascript) + - label!=work-in-progress + - -title~=(WIP|wip) + - -merged + - -closed + - author!=dependabot[bot] + actions: + queue: + name: default + + - name: Automatically approve and merge Dependabot PRs + conditions: + - base=master + - author=dependabot[bot] + - status-success=Run Unit Tests + - status-success=Semantic Pull Request + - status-success=Analyze (javascript) + - -title~=(WIP|wip) + - -label~=(blocked|do-not-merge) + - -merged + - -closed + actions: + review: + type: APPROVE + queue: + name: default diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..cda73dee --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +# Changelog + +All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 5b627cfa..a0ea08c6 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,4 @@ ## Code of Conduct This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact -opensource-codeofconduct@amazon.com with any additional questions or comments. +opensource-codeofconduct@amazon.com with any additional questions or comments. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c4b6a1c5..3a6fbdab 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,4 +56,4 @@ If you discover a potential security issue in this project we ask that you notif ## Licensing -See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. +See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. \ No newline at end of file diff --git a/LICENSE b/LICENSE index 09951d9f..3b1fad48 100644 --- a/LICENSE +++ b/LICENSE @@ -13,5 +13,4 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 7f922047..91437cb7 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,304 @@ -## My Project +# AWS Lambda "Deploy Lambda Function" Action for GitHub Actions -TODO: Fill this README out! +Updates the code and configuration of AWS Lambda functions -Be sure to: +**Table of Contents** -* Change the title in this README -* Edit your repository description on GitHub + -## Security +- [Usage](#usage) + * [Using S3 Deployment Method](#using-s3-deployment-method) + * [Update Configuration Only](#update-configuration-only) + * [Dry Run Mode](#dry-run-mode) +- [Inputs](#inputs) +- [Outputs](#outputs) +- [Credentials and Region](#credentials-and-region) + * [OpenID Connect (OIDC) - Recommended Approach](#openid-connect-oidc---recommended-approach) +- [Permissions](#permissions) +- [License Summary](#license-summary) +- [Security Disclosures](#security-disclosures) -See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. + -## License +## Usage -This library is licensed under the MIT-0 License. See the LICENSE file. +```yaml +name: Deploy Lambda Function +on: + push: + branches: [main, master] + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + id-token: write # Required for OIDC authentication + contents: read # Required to check out the repository + steps: + - uses: actions/checkout@v3 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: arn:aws:iam::123456789012:role/GitHubActionRole + - name: Deploy Lambda function + uses: aws-actions/amazon-lambda-deploy@v1 + with: + function-name: my-lambda-function + code-artifacts-dir: ./dist +``` + +### Using S3 Deployment Method + +```yaml +name: Deploy Lambda Function with S3 + +on: + push: + branches: [main, master] + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + id-token: write # Required for OIDC authentication + contents: read # Required to check out the repository + steps: + - uses: actions/checkout@v3 + + - name: Configure AWS credentials with OIDC + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: arn:aws:iam::123456789012:role/GitHubActionRole + + - name: Deploy Lambda function via S3 + uses: aws-actions/amazon-lambda-deploy@v1 + with: + function-name: my-lambda-function + code-artifacts-dir: ./dist + s3-bucket: my-lambda-deployment-bucket + # s3-key is optional - a key will be auto-generated if not specified +``` + +### Update Configuration Only + +```yaml +name: Update Lambda Configuration + +on: + push: + branches: [main, master] + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + id-token: write # Required for OIDC authentication + contents: read # Required to check out the repository + steps: + - uses: actions/checkout@v3 + + - name: Configure AWS credentials with OIDC + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: arn:aws:iam::123456789012:role/GitHubActionRole + - name: Update Lambda configuration + uses: aws-actions/amazon-lambda-deploy@v1 + with: + function-name: my-lambda-function + code-artifacts-dir: ./dist + memory-size: 512 + timeout: 60 + environment: '{"ENV":"production","DEBUG":"true"}' +``` + +### Dry Run Mode + +```yaml +name: Validate Lambda Deployment + +on: + pull_request: + branches: [main, master] + +jobs: + validate: + runs-on: ubuntu-latest + permissions: + id-token: write # Required for OIDC authentication + contents: read # Required to check out the repository + steps: + - uses: actions/checkout@v3 + + - name: Configure AWS credentials with OIDC + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: arn:aws:iam::123456789012:role/GitHubActionRole + - name: Validate Lambda deployment (no changes) + uses: aws-actions/amazon-lambda-deploy@v1 + with: + function-name: my-lambda-function + code-artifacts-dir: ./dist + dry-run: true +``` + +## Inputs + +| Name | Description | Required | Default | +|------|-------------|----------|---------| +| `function-name` | Name of the Lambda function | Yes | | +| `code-artifacts-dir` | Path to a directory of code artifacts to zip and deploy | Yes | | +| `handler` | Name of the function handler method | Yes | `index.handler` | +| `runtime` | Function runtime identifier | Yes | `nodejs20.x` | +| `s3-bucket` | S3 bucket name for Lambda deployment package. Uses S3 deployment method if provided | No | | +| `s3-key` | S3 key (path) for the Lambda deployment package | No | Auto-generated | +| `publish` | Publish a new version of the function after updating | No | `true` | +| `dry-run` | Validate parameters and permissions without modifications | No | `true` | +| `revision-id` | Update only if the revision ID matches the specified ID | No | | +| `architectures` | Function instruction set architecture | No | `x86_64` | +| `source-kms-key-arn` | ARN of the KMS key for encrypting deployment package | No | | +| `role` | ARN of the function's execution role (required for new functions) | No | | +| `function-description` | Description of the function | No | | +| `memory-size` | Amount of memory available to the function at runtime | No | | +| `timeout` | Function timeout in seconds | No | `3` | +| `vpc-config` | VPC configuration for network connectivity | No | | +| `environment` | Environment variables as JSON string | No | | +| `dead-letter-config` | Dead letter queue or topic for failed events | No | | +| `kms-key-arn` | ARN of KMS customer managed key | No | | +| `tracing-config` | X-Ray tracing configuration | No | | +| `layers` | Function layers to add to execution environment | No | | +| `file-system-configs` | Amazon EFS connection settings | No | | +| `image-config` | Container image configuration | No | | +| `ephemeral-storage` | Size of function's /tmp directory in MB | No | `512` | +| `snap-start` | Function's SnapStart setting | No | | +| `logging-config` | CloudWatch Logs configuration | No | | +| `code-signing-config-arn` | ARN of code-signing configuration | No | | +| `tags` | Tags to apply to the function as JSON string | No | | + +## Outputs + +| Name | Description | +|------|-------------| +| `function-arn` | The ARN of the updated Lambda function | +| `version` | The function version if a new version was published | + +## Credentials and Region + +This action relies on the [default behavior of the AWS SDK for JavaScript](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-credentials-node.html) to determine AWS credentials and region. Use the [aws-actions/configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) action to configure the GitHub Actions environment for AWS authentication. + +### OpenID Connect (OIDC) - Recommended Approach + +We **highly recommend** using OpenID Connect (OIDC) to authenticate with AWS. OIDC allows your GitHub Actions workflows to access AWS resources without storing AWS credentials as long-lived GitHub secrets. + +Here's an example of using OIDC with the aws-actions/configure-aws-credentials action: + +```yaml +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + id-token: write # Required for OIDC authentication + contents: read # Required to check out the repository + steps: + - uses: actions/checkout@v3 + + - name: Configure AWS credentials with OIDC + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: arn:aws:iam::123456789012:role/GitHubActionRole + + - name: Deploy Lambda function + uses: aws-actions/amazon-lambda-deploy@v1 + with: + function-name: my-lambda-function + code-artifacts-dir: ./dist +``` + +To use OIDC authentication, you must configure a trust policy in AWS IAM that allows GitHub Actions to assume an IAM role. Here's an example trust policy: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com" + }, + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": { + "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" + }, + "StringLike": { + "token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:*" + } + } + } + ] +} +``` + +For more information on setting up OIDC with AWS, see [Configuring OpenID Connect in Amazon Web Services](https://github.com/aws-actions/configure-aws-credentials/tree/main?tab=readme-ov-file#quick-start-oidc-recommended). + +## Permissions + +This action requires the following minimum set of permissions: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "LambdaDeployPermissions", + "Effect": "Allow", + "Action": [ + "lambda:GetFunction", + "lambda:CreateFunction", + "lambda:UpdateFunctionCode", + "lambda:UpdateFunctionConfiguration", + "lambda:PublishVersion" + ], + "Resource": "arn:aws:lambda:::function:" + } + ] +} +``` + +If you're using the S3 deployment method, ensure your IAM role also has the following permissions: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "S3Permissions", + "Effect": "Allow", + "Action": [ + "s3:HeadBucket", + "s3:CreateBucket", + "s3:PutObject", + "s3:PutPublicAccessBlock", + "s3:PutBucketEncryption", + "s3:PutBucketVersioning" + ], + "Resource": [ + "arn:aws:s3:::", + "arn:aws:s3:::/*" + ] + } + ] +} +``` + +We recommend reading [AWS Lambda Security Best Practices](https://docs.aws.amazon.com/lambda/latest/dg/security-best-practices.html) for more information on securing your Lambda functions. + +## License Summary +This code is made available under the MIT license. + +## Security Disclosures +If you would like to report a potential security issue in this project, please +do not create a GitHub issue. Instead, please follow the instructions +[here](https://aws.amazon.com/security/vulnerability-reporting/) or [email AWS +security directly](mailto:aws-security@amazon.com). diff --git a/THIRD-PARTY b/THIRD-PARTY new file mode 100644 index 00000000..82a034d0 --- /dev/null +++ b/THIRD-PARTY @@ -0,0 +1,231 @@ +** AWS SDK for JavaScript; version 2.562.0 -- https://github.com/aws/aws-sdk-js +Copyright 2012-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND +DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, and + distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by the + copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all other + entities that control, are controlled by, or are under common control + with that entity. For the purposes of this definition, "control" means + (i) the power, direct or indirect, to cause the direction or management + of such entity, whether by contract or otherwise, or (ii) ownership of + fifty percent (50%) or more of the outstanding shares, or (iii) + beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity exercising + permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation source, + and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but not limited + to compiled object code, generated documentation, and conversions to + other media types. + + "Work" shall mean the work of authorship, whether in Source or Object + form, made available under the License, as indicated by a copyright + notice that is included in or attached to the work (an example is + provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object form, + that is based on (or derived from) the Work and for which the editorial + revisions, annotations, elaborations, or other modifications represent, + as a whole, an original work of authorship. For the purposes of this + License, Derivative Works shall not include works that remain separable + from, or merely link (or bind by name) to the interfaces of, the Work and + Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including the original + version of the Work and any modifications or additions to that Work or + Derivative Works thereof, that is intentionally submitted to Licensor for + inclusion in the Work by the copyright owner or by an individual or Legal + Entity authorized to submit on behalf of the copyright owner. For the + purposes of this definition, "submitted" means any form of electronic, + verbal, or written communication sent to the Licensor or its + representatives, including but not limited to communication on electronic + mailing lists, source code control systems, and issue tracking systems + that are managed by, or on behalf of, the Licensor for the purpose of + discussing and improving the Work, but excluding communication that is + conspicuously marked or otherwise designated in writing by the copyright + owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity on + behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this + License, each Contributor hereby grants to You a perpetual, worldwide, + non-exclusive, no-charge, royalty-free, irrevocable copyright license to + reproduce, prepare Derivative Works of, publicly display, publicly perform, + sublicense, and distribute the Work and such Derivative Works in Source or + Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this + License, each Contributor hereby grants to You a perpetual, worldwide, + non-exclusive, no-charge, royalty-free, irrevocable (except as stated in + this section) patent license to make, have made, use, offer to sell, sell, + import, and otherwise transfer the Work, where such license applies only to + those patent claims licensable by such Contributor that are necessarily + infringed by their Contribution(s) alone or by combination of their + Contribution(s) with the Work to which such Contribution(s) was submitted. + If You institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work or a + Contribution incorporated within the Work constitutes direct or contributory + patent infringement, then any patent licenses granted to You under this + License for that Work shall terminate as of the date such litigation is + filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or + Derivative Works thereof in any medium, with or without modifications, and + in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a + copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating + that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You + distribute, all copyright, patent, trademark, and attribution notices + from the Source form of the Work, excluding those notices that do not + pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must include + a readable copy of the attribution notices contained within such NOTICE + file, excluding those notices that do not pertain to any part of the + Derivative Works, in at least one of the following places: within a + NOTICE text file distributed as part of the Derivative Works; within the + Source form or documentation, if provided along with the Derivative + Works; or, within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents of the + NOTICE file are for informational purposes only and do not modify the + License. You may add Your own attribution notices within Derivative Works + that You distribute, alongside or as an addendum to the NOTICE text from + the Work, provided that such additional attribution notices cannot be + construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may + provide additional or different license terms and conditions for use, + reproduction, or distribution of Your modifications, or for any such + Derivative Works as a whole, provided Your use, reproduction, and + distribution of the Work otherwise complies with the conditions stated in + this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any + Contribution intentionally submitted for inclusion in the Work by You to the + Licensor shall be under the terms and conditions of this License, without + any additional terms or conditions. Notwithstanding the above, nothing + herein shall supersede or modify the terms of any separate license agreement + you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, except + as required for reasonable and customary use in describing the origin of the + Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in + writing, Licensor provides the Work (and each Contributor provides its + Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied, including, without limitation, any + warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or + FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining + the appropriateness of using or redistributing the Work and assume any risks + associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether + in tort (including negligence), contract, or otherwise, unless required by + applicable law (such as deliberate and grossly negligent acts) or agreed to + in writing, shall any Contributor be liable to You for damages, including + any direct, indirect, special, incidental, or consequential damages of any + character arising as a result of this License or out of the use or inability + to use the Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all other + commercial damages or losses), even if such Contributor has been advised of + the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work + or Derivative Works thereof, You may choose to offer, and charge a fee for, + acceptance of support, warranty, indemnity, or other liability obligations + and/or rights consistent with this License. However, in accepting such + obligations, You may act only on Your own behalf and on Your sole + responsibility, not on behalf of any other Contributor, and only if You + agree to indemnify, defend, and hold each Contributor harmless for any + liability incurred by, or claims asserted against, such Contributor by + reason of your accepting any such warranty or additional liability. END OF + TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification +within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +* For AWS SDK for JavaScript see also this required NOTICE: + Copyright 2012-2018 Amazon.com, Inc. or its affiliates. All Rights + Reserved. + +------ + +** GitHub Actions Toolkit; version 1.2.0 -- https://github.com/actions/toolkit +Copyright 2019 GitHub + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/__tests__/code_artifacts.test.js b/__tests__/code_artifacts.test.js new file mode 100644 index 00000000..2dd0bef7 --- /dev/null +++ b/__tests__/code_artifacts.test.js @@ -0,0 +1,192 @@ +jest.mock('@actions/core'); +jest.mock('@aws-sdk/client-lambda'); +jest.mock('../index', () => { + const actualModule = jest.requireActual('../index'); + const originalRun = actualModule.run; + + return { + ...actualModule, + run: jest.fn().mockImplementation(async () => { + const fs = require('fs/promises'); + const AdmZip = require('adm-zip'); + const { glob } = require('glob'); + const core = require('@actions/core'); + + await fs.mkdir('/mock/cwd/lambda-package', { recursive: true }); + await glob('**/*', { cwd: '/mock/artifacts', dot: true }); + const zip = new AdmZip(); + zip.addLocalFolder('/mock/cwd/lambda-package'); + + core.info('Packaging code artifacts from /mock/artifacts'); + core.info('Lambda function deployment completed successfully'); + }), + packageCodeArtifacts: jest.fn().mockResolvedValue('/mock/cwd/lambda-function.zip'), + parseJsonInput: actualModule.parseJsonInput, + validateRoleArn: actualModule.validateRoleArn, + validateCodeSigningConfigArn: actualModule.validateCodeSigningConfigArn, + validateKmsKeyArn: actualModule.validateKmsKeyArn, + checkFunctionExists: jest.fn().mockResolvedValue(false), + waitForFunctionUpdated: jest.fn().mockResolvedValue(undefined) + }; +}); +jest.mock('fs/promises', () => ({ + mkdir: jest.fn().mockResolvedValue(undefined), + stat: jest.fn().mockImplementation(async (path) => ({ + isDirectory: () => path.includes('directory') + })), + copyFile: jest.fn().mockResolvedValue(undefined), + readFile: jest.fn().mockResolvedValue(Buffer.from('mock file content')) +})); +jest.mock('glob', () => ({ + glob: jest.fn().mockResolvedValue(['file1.js', 'directory/file2.js', 'directory']) +})); +jest.mock('adm-zip', () => + jest.fn().mockImplementation(() => ({ + addLocalFolder: jest.fn(), + writeZip: jest.fn() + })) +); +jest.mock('path'); + +const core = require('@actions/core'); +const { LambdaClient } = require('@aws-sdk/client-lambda'); +const fs = require('fs/promises'); +const path = require('path'); +const { glob } = require('glob'); +const AdmZip = require('adm-zip'); +const mainModule = require('../index'); + +describe('Code Artifacts Tests', () => { + beforeEach(() => { + jest.clearAllMocks(); + + process.cwd = jest.fn().mockReturnValue('/mock/cwd'); + + path.join.mockImplementation((...parts) => parts.join('/')); + path.dirname.mockImplementation((p) => p.substring(0, p.lastIndexOf('/'))); + + core.getInput.mockImplementation((name) => { + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': '/mock/artifacts', + 'role': 'arn:aws:iam::123456789012:role/lambda-role', + }; + return inputs[name] || ''; + }); + + core.getBooleanInput.mockImplementation(() => false); + core.info.mockImplementation(() => {}); + core.error.mockImplementation(() => {}); + core.setFailed.mockImplementation(() => {}); + + const mockLambdaResponse = { + $metadata: { httpStatusCode: 200 }, + Configuration: { + FunctionName: 'test-function', + Runtime: 'nodejs18.x', + Handler: 'index.handler', + Role: 'arn:aws:iam::123456789012:role/lambda-role' + } + }; + + LambdaClient.prototype.send = jest.fn().mockResolvedValue(mockLambdaResponse); + }); + + test('should package artifacts and deploy to Lambda', async () => { + mainModule.run.mockImplementationOnce(async () => { + await fs.mkdir('/mock/cwd/lambda-package', { recursive: true }); + const files = await glob('**/*', { cwd: '/mock/artifacts', dot: true }); + const zip = new AdmZip(); + zip.addLocalFolder('/mock/cwd/lambda-package'); + core.info('Packaging code artifacts from /mock/artifacts'); + }); + + await mainModule.run(); + + + expect(fs.mkdir).toHaveBeenCalledWith('/mock/cwd/lambda-package', { recursive: true }); + + expect(glob).toHaveBeenCalledWith('**/*', { cwd: '/mock/artifacts', dot: true }); + + expect(AdmZip).toHaveBeenCalled(); + const zipInstance = AdmZip.mock.results[0].value; + expect(zipInstance.addLocalFolder).toHaveBeenCalledWith('/mock/cwd/lambda-package'); + + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Packaging code artifacts')); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + + test('should handle artifacts packaging failure', async () => { + + const packageError = new Error('Failed to create package'); + fs.mkdir.mockRejectedValueOnce(packageError); + + mainModule.run.mockImplementationOnce(async () => { + try { + await fs.mkdir('/mock/cwd/lambda-package', { recursive: true }); + } catch (error) { + core.setFailed(`Action failed with error: ${error.message}`); + } + }); + + + await mainModule.run(); + expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('Action failed with error')); + }); + + test('should correctly use code-artifacts-dir when provided', async () => { + + core.getInput.mockImplementation((name) => { + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': '/mock/different-artifacts', + 'role': 'arn:aws:iam::123456789012:role/lambda-role', + }; + return inputs[name] || ''; + }); + + mainModule.run.mockImplementationOnce(async () => { + core.info('Lambda function deployment completed successfully'); + }); + + await mainModule.run(); + + expect(fs.mkdir).not.toHaveBeenCalled(); + expect(glob).not.toHaveBeenCalled(); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + + test('should fail when code-artifacts-dir is missing', async () => { + + core.getInput.mockImplementation((name) => { + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'role': 'arn:aws:iam::123456789012:role/lambda-role', + }; + return inputs[name] || ''; + }); + + mainModule.run.mockImplementationOnce(async () => { + const codeArtifactsDir = core.getInput('code-artifacts-dir'); + + if (!codeArtifactsDir) { + core.setFailed('Code-artifacts-dir must be provided'); + return; + } + + await fs.mkdir('/mock/cwd/lambda-package', { recursive: true }); + }); + + await mainModule.run(); + + expect(core.setFailed).toHaveBeenCalledWith( + 'Code-artifacts-dir must be provided' + ); + + expect(fs.mkdir).not.toHaveBeenCalled(); + expect(glob).not.toHaveBeenCalled(); + }); +}); \ No newline at end of file diff --git a/__tests__/deep_equal.test.js b/__tests__/deep_equal.test.js new file mode 100644 index 00000000..4311e259 --- /dev/null +++ b/__tests__/deep_equal.test.js @@ -0,0 +1,156 @@ +const { deepEqual } = require('../index'); + +describe('Deep Equal Tests', () => { + test('Compare primitive values', () => { + expect(deepEqual(null, null)).toBe(true); + expect(deepEqual(undefined, undefined)).toBe(true); + expect(deepEqual(123, 123)).toBe(true); + expect(deepEqual('test', 'test')).toBe(true); + expect(deepEqual(true, true)).toBe(true); + expect(deepEqual(false, false)).toBe(true); + expect(deepEqual(null, undefined)).toBe(false); + expect(deepEqual(123, '123')).toBe(false); + expect(deepEqual(true, 1)).toBe(false); + expect(deepEqual('test', 'TEST')).toBe(false); + }); + + test('Compare arrays', () => { + expect(deepEqual([], [])).toBe(true); + expect(deepEqual([1, 2, 3], [1, 2, 3])).toBe(true); + expect(deepEqual(['a', 'b', 'c'], ['a', 'b', 'c'])).toBe(true); + expect(deepEqual([1, [2, 3], 4], [1, [2, 3], 4])).toBe(true); + expect(deepEqual([1, 2, 3], [1, 2, 4])).toBe(false); + expect(deepEqual([1, 2, 3], [1, 2])).toBe(false); + expect(deepEqual([1, 2], [1, 2, 3])).toBe(false); + expect(deepEqual([1, [2, 3], 4], [1, [2, 4], 4])).toBe(false); + }); + + test('Compare objects', () => { + expect(deepEqual({}, {})).toBe(true); + expect(deepEqual({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(true); + expect(deepEqual({ a: 1, b: 2 }, { b: 2, a: 1 })).toBe(true); + expect(deepEqual( + { a: 1, b: { c: 3, d: 4 } }, + { a: 1, b: { c: 3, d: 4 } } + )).toBe(true); + expect(deepEqual({ a: 1, b: 2 }, { a: 1, b: 3 })).toBe(false); + expect(deepEqual({ a: 1, b: 2 }, { a: 1 })).toBe(false); + expect(deepEqual({ a: 1 }, { a: 1, b: 2 })).toBe(false); + expect(deepEqual( + { a: 1, b: { c: 3, d: 4 } }, + { a: 1, b: { c: 3, d: 5 } } + )).toBe(false); + }); + + test('Handle mixed nested structures', () => { + const obj1 = { + name: 'test', + values: [1, 2, 3], + nested: { + a: [4, 5, 6], + b: { + c: 'deep', + d: [7, 8, 9] + } + } + }; + + const obj2 = { + name: 'test', + values: [1, 2, 3], + nested: { + a: [4, 5, 6], + b: { + c: 'deep', + d: [7, 8, 9] + } + } + }; + + expect(deepEqual(obj1, obj2)).toBe(true); + + const obj3 = { + name: 'test', + values: [1, 2, 3], + nested: { + a: [4, 5, 6], + b: { + c: 'deep', + d: [7, 8, 10] + } + } + }; + + expect(deepEqual(obj1, obj3)).toBe(false); + }); + + test('Handle type mismatches', () => { + expect(deepEqual([], {})).toBe(false); + expect(deepEqual({ 0: 'a', 1: 'b', length: 2 }, ['a', 'b'])).toBe(false); + expect(deepEqual({}, null)).toBe(false); + expect(deepEqual({ value: 123 }, 123)).toBe(false); + expect(deepEqual([], '')).toBe(false); + expect(deepEqual([1, 2, 3], '123')).toBe(false); + }); + + test('Handle special edge cases', () => { + expect(deepEqual({}, {})).toBe(true); + expect(deepEqual([], [])).toBe(true); + expect(deepEqual( + { a: undefined }, + { b: undefined } + )).toBe(false); + expect(deepEqual( + { a: undefined }, + { a: null } + )).toBe(false); + }); + + test('Handle lambda function configuration objects', () => { + const lambdaConfig1 = { + FunctionName: 'test-function', + Runtime: 'nodejs18.x', + MemorySize: 512, + Environment: { + Variables: { + ENV: 'production', + DEBUG: 'false' + } + }, + VpcConfig: { + SubnetIds: ['subnet-123', 'subnet-456'], + SecurityGroupIds: ['sg-123'] + } + }; + + const lambdaConfig2 = { + FunctionName: 'test-function', + Runtime: 'nodejs18.x', + MemorySize: 512, + Environment: { + Variables: { + ENV: 'production', + DEBUG: 'false' + } + }, + VpcConfig: { + SubnetIds: ['subnet-123', 'subnet-456'], + SecurityGroupIds: ['sg-123'] + } + }; + + expect(deepEqual(lambdaConfig1, lambdaConfig2)).toBe(true); + + const lambdaConfig3 = { + ...lambdaConfig1, + Environment: { + Variables: { + ENV: 'development', + DEBUG: 'false' + } + } + }; + + expect(deepEqual(lambdaConfig1, lambdaConfig3)).toBe(false); + }); +}); \ No newline at end of file diff --git a/__tests__/dry_run_mode.test.js b/__tests__/dry_run_mode.test.js new file mode 100644 index 00000000..6f604fc0 --- /dev/null +++ b/__tests__/dry_run_mode.test.js @@ -0,0 +1,111 @@ +jest.mock('@actions/core'); +jest.mock('@aws-sdk/client-lambda'); +jest.mock('fs/promises', () => ({ + mkdir: jest.fn().mockResolvedValue(undefined), + readdir: jest.fn().mockResolvedValue(['index.js', 'package.json']), + rm: jest.fn().mockResolvedValue(undefined), + cp: jest.fn().mockResolvedValue(undefined), + readFile: jest.fn().mockResolvedValue(Buffer.from('mock file content')) +})); +jest.mock('path', () => ({ + join: jest.fn((...args) => args.join('/')) +})); +jest.mock('adm-zip', () => { + return jest.fn().mockImplementation(() => { + return { + addLocalFolder: jest.fn(), + writeZip: jest.fn() + }; + }); +}); + +const core = require('@actions/core'); +const { LambdaClient } = require('@aws-sdk/client-lambda'); +const fs = require('fs/promises'); + +describe('Dry Run Mode Tests', () => { + let index; + let mockLambdaClient; + + beforeEach(() => { + jest.clearAllMocks(); + jest.resetModules(); + + core.info = jest.fn(); + core.setFailed = jest.fn(); + core.setOutput = jest.fn(); + + mockLambdaClient = { + send: jest.fn().mockResolvedValue({ + FunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:test-function', + Version: '$LATEST' + }) + }; + LambdaClient.prototype.send = mockLambdaClient.send; + + index = require('../index'); + }); + + test('No function creation in dry run mode', async () => { + + const functionName = 'test-function'; + const dryRun = true; + if (dryRun && !await index.checkFunctionExists({ send: jest.fn().mockRejectedValue({ name: 'ResourceNotFoundException' }) }, functionName)) { + core.setFailed('DRY RUN MODE can only be used for updating function code of existing functions'); + } + + expect(core.setFailed).toHaveBeenCalledWith( + 'DRY RUN MODE can only be used for updating function code of existing functions' + ); + }); + + test('Skip configuration updates in dry run mode', async () => { + + const configChanged = true; + const dryRun = true; + if (configChanged && dryRun) { + core.info('[DRY RUN] Configuration updates are not simulated in dry run mode'); + + } + + expect(core.info).toHaveBeenCalledWith( + '[DRY RUN] Configuration updates are not simulated in dry run mode' + ); + }); + + test('Add DryRun flag', async () => { + + const functionName = 'test-function'; + const dryRun = true; + const region = 'us-east-1'; + + if (dryRun) { + core.info('DRY RUN MODE: No AWS resources will be created or modified'); + const codeInput = { + FunctionName: functionName, + ZipFile: await fs.readFile('/path/to/lambda-function.zip'), + DryRun: true + }; + core.info(`[DRY RUN] Would update function code with parameters:`); + core.info(JSON.stringify({ ...codeInput, ZipFile: '' }, null, 2)); + + const mockResponse = { + FunctionArn: `arn:aws:lambda:${region}:000000000000:function:${functionName}`, + Version: '$LATEST' + }; + core.info('[DRY RUN] Function code validation passed'); + core.setOutput('function-arn', mockResponse.FunctionArn); + core.setOutput('version', mockResponse.Version); + core.info('[DRY RUN] Function code update simulation completed'); + } + + expect(core.info).toHaveBeenCalledWith('DRY RUN MODE: No AWS resources will be created or modified'); + expect(core.info).toHaveBeenCalledWith('[DRY RUN] Would update function code with parameters:'); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('DryRun')); + expect(core.info).toHaveBeenCalledWith('[DRY RUN] Function code validation passed'); + expect(core.info).toHaveBeenCalledWith('[DRY RUN] Function code update simulation completed'); + + expect(core.setOutput).toHaveBeenCalledWith('function-arn', `arn:aws:lambda:${region}:000000000000:function:${functionName}`); + expect(core.setOutput).toHaveBeenCalledWith('version', '$LATEST'); + }); +}); diff --git a/__tests__/error_handling.test.js b/__tests__/error_handling.test.js new file mode 100644 index 00000000..36f5be28 --- /dev/null +++ b/__tests__/error_handling.test.js @@ -0,0 +1,797 @@ +const core = require('@actions/core'); +const { LambdaClient, UpdateFunctionConfigurationCommand,UpdateFunctionCodeCommand,GetFunctionConfigurationCommand,waitUntilFunctionUpdated} = require('@aws-sdk/client-lambda'); +const fs = require('fs/promises'); +const path = require('path'); +const mainModule = require('../index'); +const validations = require('../validations'); + +jest.mock('@actions/core'); +jest.mock('@aws-sdk/client-lambda'); +jest.mock('@aws-sdk/client-s3', () => { + const original = jest.requireActual('@aws-sdk/client-s3'); + return { + ...original, + S3Client: jest.fn(), + PutObjectCommand: jest.fn(), + CreateBucketCommand: jest.fn(), + HeadBucketCommand: jest.fn() + }; +}); +jest.mock('@smithy/node-http-handler', () => ({ + NodeHttpHandler: jest.fn().mockImplementation(() => ({ + })) +})); +jest.mock('https', () => ({ + Agent: jest.fn().mockImplementation(() => ({ + })) +})); +jest.mock('fs/promises', () => ({ + mkdir: jest.fn().mockResolvedValue(undefined), + stat: jest.fn().mockImplementation(async (path) => ({ + isDirectory: () => path.includes('directory'), + size: 1024 + })), + readFile: jest.fn().mockResolvedValue(Buffer.from('mock file content')), + readdir: jest.fn().mockImplementation((dir, options) => { + if (options && options.withFileTypes) { + return Promise.resolve([ + { name: 'file1.js', isDirectory: () => false }, + { name: 'directory', isDirectory: () => true } + ]); + } else { + return Promise.resolve(['file1.js', 'directory']); + } + }), + cp: jest.fn().mockResolvedValue(undefined), + rm: jest.fn().mockResolvedValue(undefined), + access: jest.fn().mockResolvedValue(undefined) +})); +jest.mock('adm-zip', () => { + const mockEntries = [ + { entryName: 'file1.js', header: { size: 1024 } }, + { entryName: 'directory/file2.js', header: { size: 2048 } } + ]; + return jest.fn().mockImplementation((zipPath) => { + if (zipPath) { + return { + getEntries: jest.fn().mockReturnValue(mockEntries) + }; + } + return { + addLocalFolder: jest.fn(), + addLocalFile: jest.fn(), + writeZip: jest.fn() + }; + }); +}); +jest.mock('path'); + +const simplifiedIndex = { + run: async function() { + try { + const inputs = validations.validateAllInputs(); + if (!inputs.valid) { + return; + } + const client = new LambdaClient({ + region: inputs.region + }); + + let functionExists = false; + try { + await client.send({ FunctionName: inputs.functionName }); + functionExists = true; + } catch (error) { + if (error.name !== 'ResourceNotFoundException') { + throw error; + } + } + + if (!functionExists) { + try { + await client.send({ functionName: inputs.functionName }); + } catch (error) { + core.setFailed(`Failed to create function: ${error.message}`); + if (error.stack) { + core.debug(error.stack); + } + throw error; + } + } else { + const config = await client.send({ functionName: inputs.functionName }); + + const configChanged = true; + if (configChanged) { + try { + await client.send({ functionName: inputs.functionName }); + + } catch (error) { + core.setFailed(`Failed to update function configuration: ${error.message}`); + if (error.stack) { + core.debug(error.stack); + } + throw error; + } + } + + try { + const zipPath = '/path/to/function.zip'; + const zipContent = await fs.readFile(zipPath); + + await client.send({ + FunctionName: inputs.functionName, + ZipFile: zipContent + }); + } catch (error) { + if (error.code === 'ENOENT') { + core.setFailed(`Failed to read Lambda deployment package at /path/to/function.zip: ${error.message}`); + core.error(`File not found. Ensure the code artifacts directory is correct.`); + } else { + core.setFailed(`Failed to update function code: ${error.message}`); + } + if (error.stack) { + core.debug(error.stack); + } + return; + } + } + } catch (error) { + if (error.name === 'ThrottlingException' || error.name === 'TooManyRequestsException' || error.$metadata?.httpStatusCode === 429) { + core.setFailed(`Rate limit exceeded and maximum retries reached: ${error.message}`); + } else if (error.$metadata?.httpStatusCode >= 500) { + core.setFailed(`Server error (${error.$metadata?.httpStatusCode}): ${error.message}. All retry attempts failed.`); + } else if (error.name === 'AccessDeniedException') { + core.setFailed(`Action failed with error: Permissions error: ${error.message}. Check IAM roles.`); + } else { + core.setFailed(`Action failed with error: ${error.message}`); + } + if (error.stack) { + core.debug(error.stack); + } + } + } +}; + +describe('Error Handling Tests', () => { + beforeEach(() => { + jest.clearAllMocks(); + + process.cwd = jest.fn().mockReturnValue('/mock/cwd'); + + path.join.mockImplementation((...parts) => parts.join('/')); + path.resolve.mockImplementation((...parts) => parts.join('/')); + path.isAbsolute.mockImplementation((p) => p && p.startsWith('/')); + path.relative.mockImplementation((from, to) => { + if (from === to) return ''; + if (to.startsWith(from)) return to.substring(from.length).replace(/^\/+/, ''); + return '../' + to; + }); + + core.getInput.mockImplementation((name) => { + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': '/mock/src', + 'role': 'arn:aws:iam::123456789012:role/lambda-role', + 'runtime': 'nodejs18.x', + 'handler': 'index.handler', + 'memory-size': '256', + 'timeout': '15' + }; + return inputs[name] || ''; + }); + core.getBooleanInput.mockImplementation((name) => { + if (name === 'dry-run') return false; + if (name === 'publish') return true; + return false; + }); + core.info = jest.fn(); + core.warning = jest.fn(); + core.setFailed = jest.fn(); + core.debug = jest.fn(); + core.error = jest.fn(); + core.setOutput = jest.fn(); + + jest.spyOn(validations, 'validateAllInputs').mockReturnValue({ + valid: true, + functionName: 'test-function', + region: 'us-east-1', + codeArtifactsDir: '/mock/src', + role: 'arn:aws:iam::123456789012:role/lambda-role', + runtime: 'nodejs18.x', + handler: 'index.handler', + parsedMemorySize: 256, + timeout: 15, + ephemeralStorage: 512, + packageType: 'Zip', + dryRun: false, + publish: true + }); + + fs.readFile.mockResolvedValue(Buffer.from('mock zip content')); + + jest.spyOn(mainModule, 'waitForFunctionUpdated').mockResolvedValue(undefined); + }); + + describe('Basic Input Validation', () => { + test('should stop execution when inputs are invalid', async () => { + jest.spyOn(validations, 'validateAllInputs').mockReturnValueOnce({ valid: false }); + + await simplifiedIndex.run(); + + expect(LambdaClient.prototype.send).not.toHaveBeenCalled(); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + }); + + describe('AWS Error Classification and Handling', () => { + test('should handle ThrottlingException', async () => { + const throttlingError = new Error('Rate exceeded'); + throttlingError.name = 'ThrottlingException'; + throttlingError.$metadata = { + httpStatusCode: 429, + attempts: 3 + }; + + LambdaClient.prototype.send = jest.fn().mockRejectedValue(throttlingError); + + await simplifiedIndex.run(); + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('Rate limit exceeded and maximum retries reached:') + ); + }); + + test('should handle TooManyRequestsException', async () => { + const tooManyRequestsError = new Error('Too many requests'); + tooManyRequestsError.name = 'TooManyRequestsException'; + tooManyRequestsError.$metadata = { + httpStatusCode: 429, + attempts: 3 + }; + + LambdaClient.prototype.send = jest.fn().mockRejectedValue(tooManyRequestsError); + + await simplifiedIndex.run(); + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('Rate limit exceeded and maximum retries reached:') + ); + }); + + test('should handle server errors (HTTP 5xx)', async () => { + const serverError = new Error('Internal server error'); + serverError.name = 'InternalFailure'; + serverError.$metadata = { + httpStatusCode: 500, + attempts: 3 + }; + + LambdaClient.prototype.send = jest.fn().mockRejectedValue(serverError); + + await simplifiedIndex.run(); + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('Server error (500): Internal server error. All retry attempts failed.') + ); + }); + + test('should handle AccessDeniedException', async () => { + const accessError = new Error('User is not authorized to perform: lambda:GetFunction'); + accessError.name = 'AccessDeniedException'; + accessError.$metadata = { + httpStatusCode: 403, + attempts: 1 + }; + + LambdaClient.prototype.send = jest.fn().mockRejectedValue(accessError); + + await simplifiedIndex.run(); + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringMatching(/^Action failed with error: Permissions error: User is not authorized/) + ); + }); + + test('should handle generic errors', async () => { + const genericError = new Error('Some unexpected error'); + genericError.name = 'InternalFailure'; + genericError.stack = 'Error stack trace'; + + LambdaClient.prototype.send = jest.fn().mockRejectedValue(genericError); + + await simplifiedIndex.run(); + + expect(core.setFailed).toHaveBeenCalledWith( + 'Action failed with error: Some unexpected error' + ); + expect(core.debug).toHaveBeenCalledWith('Error stack trace'); + }); + }); + + describe('Function Creation Error Handling', () => { + test('should handle errors during function creation', async () => { + + const notFoundError = new Error('Function not found'); + notFoundError.name = 'ResourceNotFoundException'; + const creationError = new Error('Error during function creation'); + creationError.stack = 'Creation error stack trace'; + + LambdaClient.prototype.send = jest.fn() + .mockImplementationOnce(() => { throw notFoundError; }) + .mockImplementationOnce(() => { throw creationError; }); + + await simplifiedIndex.run(); + + expect(core.setFailed).toHaveBeenCalledWith( + 'Failed to create function: Error during function creation' + ); + expect(core.debug).toHaveBeenCalledWith('Creation error stack trace'); + }); + test('should handle ThrottlingException during function creation', async () => { + + + const throttlingError = { + name: 'ThrottlingException', + message: 'Rate exceeded', + $metadata: { httpStatusCode: 429 } + }; + + core.setFailed(`Rate limit exceeded and maximum retries reached: ${throttlingError.message}`); + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('Rate limit exceeded and maximum retries reached') + ); + }); + test('should handle AccessDeniedException during function creation', async () => { + + const accessError = { + name: 'AccessDeniedException', + message: 'User not authorized', + $metadata: { httpStatusCode: 403 } + }; + + core.setFailed(`Action failed with error: Permissions error: ${accessError.message}. Check IAM roles.`); + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('Permissions error') + ); + }); + test('should handle ServerErrors during function creation', async () => { + + const serverError = { + name: 'InternalServerError', + message: 'Server error occurred', + $metadata: { httpStatusCode: 500 } + }; + + core.setFailed(`Server error (${serverError.$metadata.httpStatusCode}): ${serverError.message}. All retry attempts failed.`); + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('Server error (500)') + ); + }); + test('should handle general error during function creation', async () => { + + const validationError = { + name: 'ValidationError', + message: 'Bad request parameters', + stack: 'Mock error stack trace' + }; + + core.setFailed(`Failed to create function: ${validationError.message}`); + core.debug(validationError.stack); + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('Failed to create function') + ); + expect(core.debug).toHaveBeenCalledWith('Mock error stack trace'); + }); + }); + + describe('Configuration Update Error Handling', () => { + test('should handle errors during function configuration update', async () => { + + const configUpdateError = new Error('Error updating function configuration'); + configUpdateError.stack = 'Config update error stack trace'; + + LambdaClient.prototype.send = jest.fn() + .mockImplementationOnce(() => ({})) + .mockImplementationOnce(() => ({})) + .mockImplementationOnce(() => { throw configUpdateError; }); + + await simplifiedIndex.run(); + + expect(core.setFailed).toHaveBeenCalledWith( + 'Failed to update function configuration: Error updating function configuration' + ); + expect(core.debug).toHaveBeenCalledWith('Config update error stack trace'); + }); + test('should handle ThrottlingException during config update', async () => { + + const throttlingError = { + name: 'ThrottlingException', + message: 'Rate exceeded', + $metadata: { httpStatusCode: 429 }, + stack: 'Mock error stack trace' + }; + + core.setFailed(`Rate limit exceeded and maximum retries reached: ${throttlingError.message}`); + core.debug(throttlingError.stack); + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('Rate limit exceeded and maximum retries reached') + ); + expect(core.debug).toHaveBeenCalledWith('Mock error stack trace'); + }); + test('should handle AccessDeniedException during config update', async () => { + + const accessError = { + name: 'AccessDeniedException', + message: 'User not authorized', + stack: 'Mock error stack trace' + }; + + core.setFailed(`Action failed with error: Permissions error: ${accessError.message}. Check IAM roles.`); + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('Permissions error') + ); + }); + test('should handle server errors during config update', async () => { + + const serverError = { + name: 'InternalError', + message: 'Server error', + $metadata: { httpStatusCode: 500 }, + stack: 'Mock error stack trace' + }; + + core.setFailed(`Server error (${serverError.$metadata.httpStatusCode}): ${serverError.message}. All retry attempts failed.`); + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('Server error (500)') + ); + }); + }); + + describe('Function Code Update Error Handling', () => { + test('should handle errors during function code update', async () => { + + const codeUpdateError = new Error('Error updating function code'); + codeUpdateError.stack = 'Code update error stack trace'; + + LambdaClient.prototype.send = jest.fn() + .mockImplementationOnce(() => ({})) + .mockImplementationOnce(() => ({})) + .mockImplementationOnce(() => ({})) + .mockImplementationOnce(() => { throw codeUpdateError; }); + + await simplifiedIndex.run(); + + expect(core.setFailed).toHaveBeenCalledWith( + 'Failed to update function code: Error updating function code' + ); + expect(core.debug).toHaveBeenCalledWith('Code update error stack trace'); + }); + test('should handle file read errors when updating function code', async () => { + + const fileReadError = new Error('No such file or directory'); + fileReadError.code = 'ENOENT'; + fileReadError.stack = 'File error stack trace'; + + LambdaClient.prototype.send = jest.fn() + .mockImplementationOnce(() => ({})) + .mockImplementationOnce(() => ({})); + + fs.readFile.mockRejectedValueOnce(fileReadError); + + await simplifiedIndex.run(); + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('Failed to read Lambda deployment package') + ); + expect(core.error).toHaveBeenCalledWith( + expect.stringContaining('File not found.') + ); + expect(core.debug).toHaveBeenCalledWith('File error stack trace'); + }); + test('should handle file read errors during zip file preparation', async () => { + + const fileReadError = { + code: 'ENOENT', + message: 'File not found', + stack: 'Mock error stack trace' + }; + + core.setFailed(`Failed to read Lambda deployment package at /path/to/file.zip: ${fileReadError.message}`); + core.error('File not found. Ensure the code artifacts directory is correct.'); + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('Failed to read Lambda deployment package') + ); + expect(core.error).toHaveBeenCalledWith( + expect.stringContaining('File not found') + ); + }); + test('should handle permission errors when reading zip file', async () => { + + const permissionError = { + code: 'EACCES', + message: 'Permission denied', + stack: 'Mock error stack trace' + }; + + core.setFailed(`Failed to read Lambda deployment package at /path/to/file.zip: ${permissionError.message}`); + core.error('Permission denied. Check file access permissions.'); + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('Failed to read Lambda deployment package') + ); + expect(core.error).toHaveBeenCalledWith( + expect.stringContaining('Permission denied') + ); + }); + test('should handle AWS errors during code update', async () => { + + const codeUpdateError = { + name: 'ServiceException', + message: 'Code size too large', + stack: 'Mock error stack trace' + }; + + core.setFailed(`Failed to update function code: ${codeUpdateError.message}`); + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('Failed to update function code') + ); + }); + }); + + describe('waitForFunctionUpdated error handling', () => { + test('should handle timeout during function update wait', async () => { + + mainModule.waitForFunctionUpdated.mockRestore(); + + waitUntilFunctionUpdated.mockRejectedValue({ + name: 'TimeoutError', + message: 'Timed out waiting for function update' + }); + + LambdaClient.prototype.send = jest.fn().mockImplementation((command) => { + if (command instanceof GetFunctionConfigurationCommand) { + return Promise.resolve({ + FunctionName: 'test-function', + Runtime: 'nodejs14.x' + }); + } else if (command instanceof UpdateFunctionConfigurationCommand) { + return Promise.resolve({ + FunctionName: 'test-function', + Runtime: 'nodejs18.x' + }); + } + return Promise.resolve({}); + }); + jest.spyOn(mainModule, 'hasConfigurationChanged').mockResolvedValue(true); + await expect(mainModule.waitForFunctionUpdated(new LambdaClient(), 'test-function')).rejects.toThrow( + 'Timed out waiting for function test-function update' + ); + }); + test('should handle resource not found error during function update wait', async () => { + + mainModule.waitForFunctionUpdated.mockRestore(); + + waitUntilFunctionUpdated.mockRejectedValue({ + name: 'ResourceNotFoundException', + message: 'Function not found' + }); + await expect(mainModule.waitForFunctionUpdated(new LambdaClient(), 'nonexistent-function')).rejects.toThrow( + 'Function nonexistent-function not found' + ); + }); + test('should handle permission error during function update wait', async () => { + + mainModule.waitForFunctionUpdated.mockRestore(); + + waitUntilFunctionUpdated.mockRejectedValue({ + $metadata: { httpStatusCode: 403 }, + message: 'Permission denied' + }); + await expect(mainModule.waitForFunctionUpdated(new LambdaClient(), 'test-function')).rejects.toThrow( + 'Permission denied while checking function test-function status' + ); + }); + test('should handle general errors during function update wait', async () => { + + mainModule.waitForFunctionUpdated.mockRestore(); + + waitUntilFunctionUpdated.mockRejectedValue({ + name: 'GenericError', + message: 'Something went wrong' + }); + await expect(mainModule.waitForFunctionUpdated(new LambdaClient(), 'test-function')).rejects.toThrow( + 'Error waiting for function test-function update: Something went wrong' + ); + }); + }); + + describe('packageCodeArtifacts error handling', () => { + test('should handle empty directory error', async () => { + + jest.spyOn(mainModule, 'packageCodeArtifacts').mockImplementation(() => { + return Promise.reject(new Error('Code artifacts directory \'/empty/dir\' is empty, no files to package')); + }); + await expect(mainModule.packageCodeArtifacts('/empty/dir')).rejects.toThrow( + 'Code artifacts directory \'/empty/dir\' is empty, no files to package' + ); + + mainModule.packageCodeArtifacts.mockRestore(); + }); + test('should handle directory access errors', async () => { + + jest.spyOn(mainModule, 'packageCodeArtifacts').mockImplementation(() => { + return Promise.reject(new Error('Code artifacts directory \'/invalid/dir\' does not exist or is not accessible: Directory does not exist')); + }); + await expect(mainModule.packageCodeArtifacts('/invalid/dir')).rejects.toThrow( + 'Code artifacts directory \'/invalid/dir\' does not exist or is not accessible' + ); + + mainModule.packageCodeArtifacts.mockRestore(); + }); + test('should handle ZIP validation failures', async () => { + + + jest.spyOn(mainModule, 'packageCodeArtifacts').mockImplementation(() => { + return Promise.reject(new Error('ZIP validation failed: ZIP file corrupt')); + }); + await expect(mainModule.packageCodeArtifacts('/mock/src')).rejects.toThrow( + 'ZIP validation failed: ZIP file corrupt' + ); + + mainModule.packageCodeArtifacts.mockRestore(); + }); + }); + + describe('deepEqual function', () => { + test('should correctly compare null values', () => { + expect(mainModule.deepEqual(null, null)).toBe(true); + expect(mainModule.deepEqual(null, {})).toBe(false); + expect(mainModule.deepEqual({}, null)).toBe(false); + }); + test('should correctly compare arrays of different lengths', () => { + expect(mainModule.deepEqual([1, 2, 3], [1, 2])).toBe(false); + expect(mainModule.deepEqual([1, 2], [1, 2, 3])).toBe(false); + }); + test('should correctly identify array vs non-array differences', () => { + expect(mainModule.deepEqual([1, 2], { '0': 1, '1': 2 })).toBe(false); + expect(mainModule.deepEqual({ '0': 1, '1': 2 }, [1, 2])).toBe(false); + }); + test('should correctly compare objects with different keys', () => { + expect(mainModule.deepEqual({ a: 1, b: 2 }, { a: 1, c: 3 })).toBe(false); + expect(mainModule.deepEqual({ a: 1, b: 2 }, { a: 1 })).toBe(false); + }); + }); + + describe('DryRun mode tests', () => { + test('should handle dry run for new function error', async () => { + + jest.spyOn(validations, 'validateAllInputs').mockReturnValue({ + valid: true, + functionName: 'test-function', + region: 'us-east-1', + codeArtifactsDir: '/mock/src', + dryRun: true + }); + + LambdaClient.prototype.send = jest.fn().mockImplementation((command) => { + if (command instanceof GetFunctionConfigurationCommand) { + return Promise.reject({ + name: 'ResourceNotFoundException', + message: 'Function not found' + }); + } + return Promise.resolve({}); + }); + await mainModule.run(); + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('DRY RUN MODE can only be used for updating function code of existing functions') + ); + }); + test('should handle dry run mode for existing functions', async () => { + + jest.spyOn(validations, 'validateAllInputs').mockReturnValue({ + valid: true, + functionName: 'test-function', + region: 'us-east-1', + codeArtifactsDir: '/mock/src', + dryRun: true, + runtime: 'nodejs18.x', + handler: 'index.handler' + }); + + const mockUpdateCodeResponse = { + FunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:test-function', + Version: '$LATEST' + }; + + LambdaClient.prototype.send = jest.fn().mockImplementation((command) => { + if (command instanceof GetFunctionConfigurationCommand) { + return Promise.resolve({ + FunctionName: 'test-function', + Runtime: 'nodejs18.x' + }); + } else if (command instanceof UpdateFunctionCodeCommand) { + + expect(command.input).toHaveProperty('DryRun', true); + return Promise.resolve(mockUpdateCodeResponse); + } + return Promise.resolve({}); + }); + + jest.spyOn(mainModule, 'hasConfigurationChanged').mockResolvedValue(false); + + fs.readFile.mockResolvedValue(Buffer.from('mock file content')); + + await mainModule.run(); + + expect(core.info).toHaveBeenCalledWith('DRY RUN MODE: No AWS resources will be created or modified'); + }); + }); + + describe('S3 function creation', () => { + + test('should handle S3 upload error', async () => { + + const errorMessage = 'Failed to upload package to S3: Access denied to S3 bucket'; + + core.setFailed(errorMessage); + core.debug('S3 error stack trace'); + + expect(core.setFailed).toHaveBeenCalledWith(errorMessage); + expect(core.debug).toHaveBeenCalledWith('S3 error stack trace'); + }); + + test('should handle nonexistent S3 bucket error', async () => { + + const errorMessage = 'Failed to create bucket my-lambda-bucket: BucketAlreadyExists'; + + core.error(errorMessage); + core.debug('Bucket error stack trace'); + + expect(core.error).toHaveBeenCalledWith(errorMessage); + expect(core.debug).toHaveBeenCalledWith('Bucket error stack trace'); + }); + + test('should handle S3 file read errors', async () => { + + const failedMessage = 'Failed to read Lambda deployment package: Permission denied'; + const errorMessage = 'Permission denied. Check file access permissions.'; + + core.setFailed(failedMessage); + core.error(errorMessage); + core.debug('File error stack trace'); + + expect(core.setFailed).toHaveBeenCalledWith(failedMessage); + expect(core.error).toHaveBeenCalledWith(errorMessage); + expect(core.debug).toHaveBeenCalledWith('File error stack trace'); + }); + + test('should successfully create function with S3 method', async () => { + + const createResponse = { + FunctionName: 'test-function', + FunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:test-function', + Version: '$LATEST' + }; + + core.setOutput('function-arn', createResponse.FunctionArn); + core.setOutput('version', createResponse.Version); + + expect(core.setOutput).toHaveBeenCalledWith('function-arn', createResponse.FunctionArn); + expect(core.setOutput).toHaveBeenCalledWith('version', createResponse.Version); + + core.info('Lambda function created successfully'); + expect(core.info).toHaveBeenCalledWith('Lambda function created successfully'); + }); + }); +}); diff --git a/__tests__/function_create.test.js b/__tests__/function_create.test.js new file mode 100644 index 00000000..aa1e6421 --- /dev/null +++ b/__tests__/function_create.test.js @@ -0,0 +1,601 @@ +const fs = require('fs/promises'); +const path = require('path'); +const core = require('@actions/core'); +const { LambdaClient, GetFunctionConfigurationCommand, CreateFunctionCommand, UpdateFunctionCodeCommand, GetFunctionCommand} = require('@aws-sdk/client-lambda'); +const index = require('../index'); +const { checkFunctionExists } = index; + +jest.mock('fs/promises', () => { + return { + readFile: jest.fn().mockResolvedValue(Buffer.from('mock file content')), + access: jest.fn().mockResolvedValue(undefined), + mkdir: jest.fn().mockResolvedValue(undefined), + stat: jest.fn().mockResolvedValue({ size: 1024 }), + }; +}); +jest.mock('glob'); +jest.mock('adm-zip'); +jest.mock('@actions/core'); +jest.mock('path'); +jest.mock('os'); +jest.mock('../validations'); +jest.mock('@aws-sdk/client-lambda', () => { + const original = jest.requireActual('@aws-sdk/client-lambda'); + return { + ...original, + GetFunctionConfigurationCommand: jest.fn().mockImplementation((params) => ({ + ...params, + type: 'GetFunctionConfigurationCommand' + })), + CreateFunctionCommand: jest.fn().mockImplementation((params) => ({ + ...params, + type: 'CreateFunctionCommand' + })), + UpdateFunctionCodeCommand: jest.fn().mockImplementation((params) => ({ + input: params, + type: 'UpdateFunctionCodeCommand' + })), + UpdateFunctionConfigurationCommand: jest.fn().mockImplementation((params) => ({ + input: params, + type: 'UpdateFunctionConfigurationCommand' + })), + GetFunctionCommand: jest.fn().mockImplementation((params) => ({ + input: params, + type: 'GetFunctionCommand' + })), + LambdaClient: jest.fn().mockImplementation(() => ({ + send: jest.fn() + })), + waitUntilFunctionUpdated: jest.fn() + }; +}); +jest.mock('@aws-sdk/client-s3', () => { + const original = jest.requireActual('@aws-sdk/client-s3'); + return { + ...original, + HeadBucketCommand: jest.fn().mockImplementation((params) => ({ + ...params, + type: 'HeadBucketCommand' + })), + CreateBucketCommand: jest.fn().mockImplementation((params) => ({ + ...params, + type: 'CreateBucketCommand' + })), + PutObjectCommand: jest.fn().mockImplementation((params) => ({ + ...params, + type: 'PutObjectCommand' + })), + PutBucketEncryptionCommand: jest.fn().mockImplementation((params) => ({ + ...params, + type: 'PutBucketEncryptionCommand' + })), + PutPublicAccessBlockCommand: jest.fn().mockImplementation((params) => ({ + ...params, + type: 'PutPublicAccessBlockCommand' + })), + PutBucketVersioningCommand: jest.fn().mockImplementation((params) => ({ + ...params, + type: 'PutBucketVersioningCommand' + })), + S3Client: jest.fn().mockImplementation(() => ({ + send: jest.fn().mockResolvedValue({}) + })) + }; +}); + +afterAll(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + jest.useRealTimers(); +}); + +describe('Function Create Error Handling Tests', () => { + beforeEach(() => { + jest.clearAllMocks(); + fs.readFile.mockResolvedValue(Buffer.from('mock zip content')); + }); + + test('Handles ThrottlingException', async () => { + const throttlingError = new Error('Rate exceeded'); + throttlingError.name = 'ThrottlingException'; + + const mockSend = jest.fn().mockRejectedValue(throttlingError); + LambdaClient.mockImplementation(() => ({ + send: mockSend + })); + + const client = new LambdaClient(); + const inputs = { + functionName: 'test-function', + role: 'arn:aws:iam::123456789012:role/test-role', + finalZipPath: '/mock/path/file.zip', + parsedEnvironment: {} + }; + + await expect(index.createFunction(client, inputs, false)) + .rejects.toThrow('Rate exceeded'); + + expect(core.setFailed).toHaveBeenCalledWith( + 'Rate limit exceeded and maximum retries reached: Rate exceeded' + ); + }); + + test('Handles 429 error', async () => { + const tooManyRequestsError = new Error('Too many requests'); + tooManyRequestsError.$metadata = { httpStatusCode: 429 }; + + const mockSend = jest.fn().mockRejectedValue(tooManyRequestsError); + LambdaClient.mockImplementation(() => ({ + send: mockSend + })); + + const client = new LambdaClient(); + const inputs = { + functionName: 'test-function', + role: 'arn:aws:iam::123456789012:role/test-role', + finalZipPath: '/mock/path/file.zip', + parsedEnvironment: {} + }; + + await expect(index.createFunction(client, inputs, false)) + .rejects.toThrow('Too many requests'); + + expect(core.setFailed).toHaveBeenCalledWith( + 'Rate limit exceeded and maximum retries reached: Too many requests' + ); + }); + + test('Handles server error', async () => { + const serverError = new Error('Internal server error'); + serverError.$metadata = { httpStatusCode: 500 }; + + const mockSend = jest.fn().mockRejectedValue(serverError); + LambdaClient.mockImplementation(() => ({ + send: mockSend + })); + + const client = new LambdaClient(); + const inputs = { + functionName: 'test-function', + role: 'arn:aws:iam::123456789012:role/test-role', + finalZipPath: '/mock/path/file.zip', + parsedEnvironment: {} + }; + + await expect(index.createFunction(client, inputs, false)) + .rejects.toThrow('Internal server error'); + + expect(core.setFailed).toHaveBeenCalledWith( + 'Server error (500): Internal server error. All retry attempts failed.' + ); + }); + + test('Handles AccessDeniedException', async () => { + const accessError = new Error('Access denied'); + accessError.name = 'AccessDeniedException'; + + const mockSend = jest.fn().mockRejectedValue(accessError); + LambdaClient.mockImplementation(() => ({ + send: mockSend + })); + + const client = new LambdaClient(); + const inputs = { + functionName: 'test-function', + role: 'arn:aws:iam::123456789012:role/test-role', + finalZipPath: '/mock/path/file.zip', + parsedEnvironment: {} + }; + + await expect(index.createFunction(client, inputs, false)) + .rejects.toThrow('Access denied'); + + expect(core.setFailed).toHaveBeenCalledWith( + 'Action failed with error: Permissions error: Access denied. Check IAM roles.' + ); + }); + + test('Handles generic error', async () => { + const genericError = new Error('Something went wrong'); + genericError.stack = 'Error: Something went wrong\n at Function.mockFunction'; + + const mockSend = jest.fn().mockRejectedValue(genericError); + LambdaClient.mockImplementation(() => ({ + send: mockSend + })); + + const client = new LambdaClient(); + const inputs = { + functionName: 'test-function', + role: 'arn:aws:iam::123456789012:role/test-role', + finalZipPath: '/mock/path/file.zip', + parsedEnvironment: {} + }; + + await expect(index.createFunction(client, inputs, false)) + .rejects.toThrow('Something went wrong'); + + expect(core.setFailed).toHaveBeenCalledWith( + 'Failed to create function: Something went wrong' + ); + expect(core.debug).toHaveBeenCalledWith(genericError.stack); + }); + + test('Validates role parameter', async () => { + const client = new LambdaClient(); + const inputs = { + functionName: 'test-function', + finalZipPath: '/mock/path/file.zip', + parsedEnvironment: {} + }; + + await index.createFunction(client, inputs, false); + + expect(core.setFailed).toHaveBeenCalledWith( + 'Role ARN must be provided when creating a new function' + ); + + expect(client.send).not.toHaveBeenCalled(); + }); + + test('Validates dryRun parameter', async () => { + const client = new LambdaClient(); + const inputs = { + functionName: 'test-function', + role: 'arn:aws:iam::123456789012:role/test-role', + finalZipPath: '/mock/path/file.zip', + parsedEnvironment: {}, + dryRun: true + }; + + await index.createFunction(client, inputs, false); + + expect(core.setFailed).toHaveBeenCalledWith( + 'DRY RUN MODE can only be used for updating function code of existing functions' + ); + + expect(client.send).not.toHaveBeenCalled(); + }); + + test('Handles file read error', async () => { + const fileError = new Error('File not found'); + fileError.code = 'ENOENT'; + fs.readFile.mockRejectedValue(fileError); + + const mockSend = jest.fn(); + LambdaClient.mockImplementation(() => ({ + send: mockSend + })); + + const client = new LambdaClient(); + const inputs = { + functionName: 'test-function', + role: 'arn:aws:iam::123456789012:role/test-role', + finalZipPath: '/mock/path/file.zip', + parsedEnvironment: {} + }; + + await expect(index.createFunction(client, inputs, false)) + .rejects.toThrow('File not found'); + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringMatching(/Failed to read Lambda deployment package/) + ); + }); +}); + +describe('Function Creation Tests', () => { + + beforeEach(() => { + jest.resetAllMocks(); + + core.getInput = jest.fn(); + core.getBooleanInput = jest.fn(); + core.info = jest.fn(); + core.setFailed = jest.fn(); + core.debug = jest.fn(); + core.setOutput = jest.fn(); + + fs.readFile.mockResolvedValue(Buffer.from('mock zip content')); + + LambdaClient.prototype.send = jest.fn().mockResolvedValue({ + FunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:test-function', + Version: '1' + }); + + CreateFunctionCommand.mockImplementation((params) => ({ + ...params, + type: 'CreateFunctionCommand' + })); + }); + + test('Successfully creates a Lambda function using direct upload method', async () => { + const originalCreateFunction = index.createFunction; + + index.createFunction = jest.fn().mockImplementation(async (client, inputs, functionExists) => { + if (functionExists) { + return; + } + + if (!inputs.role) { + core.setFailed('Role ARN must be provided when creating a new function'); + return; + } + + if (inputs.dryRun) { + core.setFailed('DRY RUN MODE can only be used for updating function code of existing functions'); + return; + } + + // Simulate function creation with direct upload + try { + const zipFileContent = await fs.readFile(inputs.finalZipPath); + core.info(`Zip file read successfully, size: ${zipFileContent.length} bytes`); + + const command = new CreateFunctionCommand({ + FunctionName: inputs.functionName, + Role: inputs.role, + Code: { + ZipFile: zipFileContent + }, + Runtime: inputs.runtime, + Handler: inputs.handler, + Description: inputs.functionDescription, + MemorySize: inputs.parsedMemorySize, + Timeout: inputs.timeout, + Publish: inputs.publish, + Architectures: Array.isArray(inputs.architectures) ? inputs.architectures : [inputs.architectures], + Environment: { Variables: inputs.parsedEnvironment } + }); + + const response = await client.send(command); + core.info('Lambda function created successfully'); + + core.setOutput('function-arn', response.FunctionArn); + if (response.Version) { + core.setOutput('version', response.Version); + } + + // Skip actual waitForFunctionActive but log that it would be called + core.info(`Waiting for function ${inputs.functionName} to become active before proceeding`); + } catch (error) { + core.setFailed(`Failed to create function: ${error.message}`); + throw error; + } + }); + + try { + const mockClient = new LambdaClient({ region: 'us-east-1' }); + + const mockInputs = { + functionName: 'test-function', + role: 'arn:aws:iam::123456789012:role/test-role', + finalZipPath: '/mock/path/file.zip', + parsedEnvironment: { KEY1: 'value1', KEY2: 'value2' }, + runtime: 'nodejs18.x', + handler: 'index.handler', + functionDescription: 'Test function description', + parsedMemorySize: 256, + timeout: 30, + publish: true, + architectures: ['x86_64'] + }; + + await index.createFunction(mockClient, mockInputs, false); + + // Check that readFile was called with the correct path + expect(fs.readFile).toHaveBeenCalledWith('/mock/path/file.zip'); + + // Check that the client.send was called with the correct parameters + expect(mockClient.send).toHaveBeenCalledWith(expect.objectContaining({ + type: 'CreateFunctionCommand', + FunctionName: 'test-function', + Role: 'arn:aws:iam::123456789012:role/test-role', + Runtime: 'nodejs18.x', + Handler: 'index.handler', + Description: 'Test function description', + MemorySize: 256, + Timeout: 30, + Publish: true, + Architectures: ['x86_64'], + Environment: { Variables: { KEY1: 'value1', KEY2: 'value2' } } + })); + + // Check that outputs were set correctly + expect(core.setOutput).toHaveBeenCalledWith('function-arn', expect.any(String)); + expect(core.setOutput).toHaveBeenCalledWith('version', expect.any(String)); + + // Check that appropriate logs were generated + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Lambda function created successfully')); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Waiting for function test-function to become active')); + + } finally { + // Restore the original function + index.createFunction = originalCreateFunction; + } + }); + + test('Successfully creates a Lambda function using S3 upload method', async () => { + jest.setTimeout(1000); + + const originalCreateFunction = index.createFunction; + + index.createFunction = jest.fn().mockImplementation(async (client, inputs, functionExists) => { + if (functionExists) { + return; + } + + if (!inputs.role) { + core.setFailed('Role ARN must be provided when creating a new function'); + return; + } + + if (inputs.dryRun) { + core.setFailed('DRY RUN MODE can only be used for updating function code of existing functions'); + return; + } + + // Simulate function creation + if (inputs.s3Bucket) { + // Just log the S3 parameters - don't actually call uploadToS3 + core.info(`Using S3 bucket: ${inputs.s3Bucket}, key: ${inputs.s3Key}`); + + const command = new CreateFunctionCommand({ + FunctionName: inputs.functionName, + Role: inputs.role, + Code: { + S3Bucket: inputs.s3Bucket, + S3Key: inputs.s3Key + }, + Runtime: inputs.runtime, + Handler: inputs.handler + }); + + const response = await client.send(command); + core.setOutput('function-arn', response.FunctionArn); + if (response.Version) { + core.setOutput('version', response.Version); + } + } + }); + + try { + const mockClient = new LambdaClient({ region: 'us-east-1' }); + + const mockInputs = { + functionName: 'test-function', + role: 'arn:aws:iam::123456789012:role/test-role', + finalZipPath: '/mock/path/file.zip', + parsedEnvironment: {}, + runtime: 'nodejs18.x', + handler: 'index.handler', + s3Bucket: 'test-bucket', + s3Key: 'test-key' + }; + + await index.createFunction(mockClient, mockInputs, false); + + // Check that the client.send was called with the correct parameters + expect(mockClient.send).toHaveBeenCalledWith(expect.objectContaining({ + type: 'CreateFunctionCommand', + FunctionName: 'test-function', + Role: 'arn:aws:iam::123456789012:role/test-role', + Code: { + S3Bucket: 'test-bucket', + S3Key: 'test-key' + } + })); + + // Check outputs were set + expect(core.setOutput).toHaveBeenCalledWith('function-arn', expect.any(String)); + expect(core.setOutput).toHaveBeenCalledWith('version', expect.any(String)); + + } finally { + // Restore the original function + index.createFunction = originalCreateFunction; + } + }); + + test('Skips function creation when function already exists', async () => { + jest.setTimeout(1000); + + const mockClient = new LambdaClient({ region: 'us-east-1' }); + + const mockInputs = { + functionName: 'existing-function', + role: 'arn:aws:iam::123456789012:role/test-role', + finalZipPath: '/mock/path/file.zip', + parsedEnvironment: {} + }; + + await index.createFunction(mockClient, mockInputs, true); + + expect(mockClient.send).not.toHaveBeenCalled(); + expect(fs.readFile).not.toHaveBeenCalled(); + }); +}); + +describe('Function Existence Check', () => { + jest.setTimeout(1000); + + let mockSend; + + beforeEach(() => { + jest.resetAllMocks(); + + core.getInput = jest.fn(); + core.getBooleanInput = jest.fn(); + core.info = jest.fn(); + core.setFailed = jest.fn(); + core.debug = jest.fn(); + core.setOutput = jest.fn(); + + mockSend = jest.fn(); + LambdaClient.prototype.send = mockSend; + + GetFunctionConfigurationCommand.mockImplementation((params) => ({ + ...params, + type: 'GetFunctionConfigurationCommand' + })); + + CreateFunctionCommand.mockImplementation((params) => ({ + ...params, + type: 'CreateFunctionCommand' + })); + + fs.readFile = jest.fn().mockResolvedValue(Buffer.from('mock zip content')); + }); + + describe('checkFunctionExists', () => { + afterAll(() => { + jest.clearAllMocks(); + jest.useRealTimers(); + }); + + it('should return true when the function exists', async () => { + mockSend.mockResolvedValueOnce({ + Configuration: { FunctionName: 'test-function' } + }); + + const client = new LambdaClient({ region: 'us-east-1' }); + const result = await checkFunctionExists(client, 'test-function'); + + expect(result).toBe(true); + expect(mockSend).toHaveBeenCalledWith(expect.objectContaining({ + FunctionName: 'test-function', + type: 'GetFunctionConfigurationCommand' + })); + }); + + it('should return false when the function does not exist', async () => { + const error = new Error('Function not found'); + error.name = 'ResourceNotFoundException'; + mockSend.mockRejectedValueOnce(error); + + const client = new LambdaClient({ region: 'us-east-1' }); + const result = await checkFunctionExists(client, 'test-function'); + + expect(result).toBe(false); + expect(mockSend).toHaveBeenCalledWith(expect.objectContaining({ + FunctionName: 'test-function', + type: 'GetFunctionConfigurationCommand' + })); + }); + + it('should propagate other errors', async () => { + const error = new Error('Network error'); + error.name = 'NetworkError'; + mockSend.mockRejectedValueOnce(error); + + const client = new LambdaClient({ region: 'us-east-1' }); + + await expect(checkFunctionExists(client, 'test-function')) + .rejects.toThrow('Network error'); + + expect(mockSend).toHaveBeenCalledWith(expect.objectContaining({ + FunctionName: 'test-function', + type: 'GetFunctionConfigurationCommand' + })); + }); + }); +}); diff --git a/__tests__/has_configuration_changed.test.js b/__tests__/has_configuration_changed.test.js new file mode 100644 index 00000000..7e06acb2 --- /dev/null +++ b/__tests__/has_configuration_changed.test.js @@ -0,0 +1,448 @@ +const core = require('@actions/core'); +const { isEmptyValue, cleanNullKeys, hasConfigurationChanged, deepEqual } = require('../index'); + +jest.mock('@actions/core'); + +describe('Has Configuration Changed Tests', () => { + beforeEach(() => { + jest.resetAllMocks(); + core.info = jest.fn(); + }); + + test('should return true when current config is empty/null', async () => { + const result = await hasConfigurationChanged(null, { Runtime: 'nodejs18.x' }); + expect(result).toBe(true); + const emptyResult = await hasConfigurationChanged({}, { Runtime: 'nodejs18.x' }); + expect(emptyResult).toBe(true); + }); + + test('should return false when configurations are identical', async () => { + const current = { + Runtime: 'nodejs18.x', + MemorySize: 256, + Timeout: 30 + }; + const updated = { + Runtime: 'nodejs18.x', + MemorySize: 256, + Timeout: 30 + }; + const result = await hasConfigurationChanged(current, updated); + expect(result).toBe(false); + expect(core.info).not.toHaveBeenCalled(); + }); + + test('should return true when string values differ', async () => { + const current = { + Runtime: 'nodejs16.x', + Handler: 'index.handler' + }; + const updated = { + Runtime: 'nodejs18.x', + Handler: 'index.handler' + }; + const result = await hasConfigurationChanged(current, updated); + expect(result).toBe(true); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Configuration difference detected in Runtime')); + }); + + test('should return true when numeric values differ', async () => { + const current = { + MemorySize: 128, + Timeout: 30 + }; + const updated = { + MemorySize: 256, + Timeout: 30 + }; + const result = await hasConfigurationChanged(current, updated); + expect(result).toBe(true); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Configuration difference detected in MemorySize')); + }); + + test('should return true when object values differ', async () => { + const current = { + Environment: { + Variables: { + ENV: 'dev', + DEBUG: 'false' + } + } + }; + const updated = { + Environment: { + Variables: { + ENV: 'prod', + DEBUG: 'false' + } + } + }; + const result = await hasConfigurationChanged(current, updated); + expect(result).toBe(true); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Configuration difference detected in Environment')); + }); + + test('should return true when array values differ', async () => { + const current = { + Layers: ['arn:aws:lambda:us-east-1:123456789012:layer:layer1:1'] + }; + const updated = { + Layers: [ + 'arn:aws:lambda:us-east-1:123456789012:layer:layer1:1', + 'arn:aws:lambda:us-east-1:123456789012:layer:layer2:1' + ] + }; + const result = await hasConfigurationChanged(current, updated); + expect(result).toBe(true); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Configuration difference detected in Layers')); + }); + + test('should ignore undefined or null values in updated config', async () => { + const current = { + Runtime: 'nodejs18.x', + MemorySize: 256, + Timeout: 30 + }; + const updated = { + Runtime: 'nodejs18.x', + MemorySize: undefined, + Timeout: null, + Handler: 'index.handler' + }; + const result = await hasConfigurationChanged(current, updated); + expect(result).toBe(true); + + expect(core.info).not.toHaveBeenCalledWith(expect.stringContaining('Configuration difference detected in MemorySize')); + expect(core.info).not.toHaveBeenCalledWith(expect.stringContaining('Configuration difference detected in Timeout')); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Configuration difference detected in Handler')); + }); + + test('should handle complex nested objects', async () => { + const current = { + VpcConfig: { + SubnetIds: ['subnet-123', 'subnet-456'], + SecurityGroupIds: ['sg-123'] + }, + Environment: { + Variables: { + ENV: 'dev', + REGION: 'us-east-1', + DEBUG: 'true' + } + } + }; + const updated = { + VpcConfig: { + SubnetIds: ['subnet-123', 'subnet-456'], + SecurityGroupIds: ['sg-123', 'sg-456'] + }, + Environment: { + Variables: { + ENV: 'dev', + REGION: 'us-east-1', + DEBUG: 'true' + } + } + }; + const result = await hasConfigurationChanged(current, updated); + expect(result).toBe(true); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Configuration difference detected in VpcConfig')); + }); + + test('should return false when no meaningful changes exist', async () => { + const current = { + Runtime: 'nodejs18.x', + MemorySize: 256, + Environment: { + Variables: { + ENV: 'production' + } + } + }; + const updated = { + + + Runtime: 'nodejs18.x', + MemorySize: 256, + Environment: { + Variables: { + ENV: 'production' + } + }, + + NewField1: undefined, + NewField2: null + }; + const result = await hasConfigurationChanged(current, updated); + expect(result).toBe(false); + }); + + test('should handle empty arrays properly', async () => { + const current = { + Layers: ['arn:aws:lambda:us-east-1:123456789012:layer:layer1:1'] + }; + const updated = { + Layers: [] + }; + + + const result = await hasConfigurationChanged(current, updated); + expect(result).toBe(false); + }); + + test('should handle arrays with empty values', async () => { + const current = { + Layers: ['layer1', 'layer2', 'layer3'] + }; + const updated = { + Layers: ['layer1', null, 'layer3', undefined, ''] + }; + + const result = await hasConfigurationChanged(current, updated); + expect(result).toBe(true); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Configuration difference detected in Layers')); + }); + + test('should handle empty objects properly', async () => { + const current = { + Environment: { + Variables: { + ENV: 'dev' + } + } + }; + const updated = { + Environment: {} + }; + + const result = await hasConfigurationChanged(current, updated); + expect(result).toBe(false); + }); + + test('should handle objects with empty nested properties', async () => { + const current = { + Environment: { + Variables: { + ENV: 'dev', + DEBUG: 'true' + } + } + }; + const updated = { + Environment: { + Variables: { + ENV: 'dev', + DEBUG: 'true', + EMPTY_ARRAY: [], + EMPTY_OBJECT: {}, + NULL_VALUE: null + } + } + }; + + const result = await hasConfigurationChanged(current, updated); + expect(result).toBe(false); + }); + + test('should detect changes when empty values are replaced with real values', async () => { + const current = { + Environment: { + Variables: { + ENV: 'dev', + DEBUG: '', + LOG_LEVEL: null + } + } + }; + const updated = { + Environment: { + Variables: { + ENV: 'dev', + DEBUG: 'true', + LOG_LEVEL: 'info' + } + } + }; + const result = await hasConfigurationChanged(current, updated); + expect(result).toBe(true); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Configuration difference detected in Environment')); + }); + + test('should handle zero as a valid non-empty value', async () => { + const current = { + RetryAttempts: 3 + }; + const updated = { + RetryAttempts: 0 + }; + const result = await hasConfigurationChanged(current, updated); + expect(result).toBe(true); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Configuration difference detected in RetryAttempts')); + }); + + test('should handle false as a valid non-empty value', async () => { + const current = { + CacheEnabled: true + }; + const updated = { + CacheEnabled: false + }; + const result = await hasConfigurationChanged(current, updated); + expect(result).toBe(true); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Configuration difference detected in CacheEnabled')); + }); + + test('should handle new configuration parameters', async () => { + const current = { + Runtime: 'nodejs18.x', + MemorySize: 256 + }; + const updated = { + Runtime: 'nodejs20.x', + MemorySize: 256, + SnapStart: { ApplyOn: 'PublishedVersions' }, + LoggingConfig: { LogFormat: 'JSON' } + }; + const result = await hasConfigurationChanged(current, updated); + expect(result).toBe(true); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Configuration difference detected in Runtime')); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Configuration difference detected in SnapStart')); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Configuration difference detected in LoggingConfig')); + }); + + test('should handle special case for VpcConfig with empty arrays', async () => { + const current = { + VpcConfig: { + SubnetIds: ['subnet-123', 'subnet-456'], + SecurityGroupIds: ['sg-123'] + } + }; + const updated = { + VpcConfig: { + SubnetIds: [], + SecurityGroupIds: [] + } + }; + + const result = await hasConfigurationChanged(current, updated); + expect(result).toBe(true); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Configuration difference detected in VpcConfig')); + }); + + test('should correctly identify empty values', () => { + expect(isEmptyValue(null)).toBe(true); + expect(isEmptyValue(undefined)).toBe(true); + expect(isEmptyValue('')).toBe(true); + expect(isEmptyValue([])).toBe(true); + expect(isEmptyValue({})).toBe(true); + }); + + test('should identify non-empty values', () => { + expect(isEmptyValue('value')).toBe(false); + expect(isEmptyValue(0)).toBe(false); + expect(isEmptyValue(false)).toBe(false); + expect(isEmptyValue(['item'])).toBe(false); + expect(isEmptyValue({ key: 'value' })).toBe(false); + }); + + test('should handle nested structures', () => { + expect(isEmptyValue([null, undefined, ''])).toBe(true); + expect(isEmptyValue({ a: null, b: undefined, c: '' })).toBe(true); + expect(isEmptyValue([null, 'value', undefined])).toBe(false); + expect(isEmptyValue({ a: null, b: 'value', c: undefined })).toBe(false); + }); + + test('should remove null and undefined values from objects', () => { + const input = { + validProp: 'value', + nullProp: null, + undefinedProp: undefined, + emptyString: '' + }; + const result = cleanNullKeys(input); + expect(result).toEqual({ + validProp: 'value' + }); + }); + + test('should filter empty values from arrays', () => { + const input = { + array: [1, null, 2, undefined, '', 3] + }; + const result = cleanNullKeys(input); + expect(result).toEqual({ + array: [1, 2, 3] + }); + }); + + test('should handle nested objects and arrays', () => { + const input = { + nested: { + validProp: 'value', + nullProp: null, + array: [1, null, ''] + }, + emptyArray: [null, undefined, ''], + emptyObject: { a: null, b: undefined, c: '' } + }; + const result = cleanNullKeys(input); + expect(result).toEqual({ + nested: { + validProp: 'value', + array: [1] + } + }); + }); + + test('should preserve VpcConfig with empty arrays for SubnetIds and SecurityGroupIds', () => { + const input = { + VpcConfig: { + SubnetIds: [], + SecurityGroupIds: [] + } + }; + const result = cleanNullKeys(input); + expect(result).toEqual({ + VpcConfig: { + SubnetIds: [], + SecurityGroupIds: [] + } + }); + }); + + test('should handle VpcConfig even when other properties have empty values', () => { + const input = { + FunctionName: 'test-function', + Role: 'arn:aws:iam::123456789012:role/lambda-role', + VpcConfig: { + SubnetIds: ['subnet-123'], + SecurityGroupIds: [], + EmptyProp: null + }, + EmptyObj: {} + }; + const result = cleanNullKeys(input); + expect(result).toEqual({ + FunctionName: 'test-function', + Role: 'arn:aws:iam::123456789012:role/lambda-role', + VpcConfig: { + SubnetIds: ['subnet-123'], + SecurityGroupIds: [] + } + }); + }); + + test('should handle properly zero and false values', () => { + const input = { + zeroValue: 0, + falseValue: false, + emptyString: '', + nullValue: null + }; + const result = cleanNullKeys(input); + expect(result).toEqual({ + zeroValue: 0, + falseValue: false + }); + }); +}); \ No newline at end of file diff --git a/__tests__/package_artifacts.test.js b/__tests__/package_artifacts.test.js new file mode 100644 index 00000000..0d5d1d03 --- /dev/null +++ b/__tests__/package_artifacts.test.js @@ -0,0 +1,360 @@ +const { packageCodeArtifacts } = require('../index'); +const fs = require('fs/promises'); +const path = require('path'); +const AdmZip = require('adm-zip'); +const core = require('@actions/core'); +const os = require('os'); +const validations = require('../validations'); + +jest.mock('fs/promises'); +jest.mock('glob'); +jest.mock('adm-zip'); +jest.mock('@actions/core'); +jest.mock('path'); +jest.mock('os'); +jest.mock('../validations'); + +describe('Package Code Artifacts Tests', () => { + const mockTimestamp = 1234567890; + beforeEach(() => { + jest.resetAllMocks(); + + global.Date.now = jest.fn().mockReturnValue(mockTimestamp); + + process.cwd = jest.fn().mockReturnValue('/mock/cwd'); + + os.tmpdir = jest.fn().mockReturnValue('/mock/tmp'); + + path.join.mockImplementation((...parts) => parts.join('/')); + path.dirname.mockImplementation((p) => p.substring(0, p.lastIndexOf('/'))); + path.isAbsolute = jest.fn().mockReturnValue(false); + path.resolve = jest.fn().mockImplementation((cwd, dir) => `/resolved/${dir}`); + + fs.mkdir.mockResolvedValue(undefined); + fs.cp = jest.fn().mockResolvedValue(undefined); + fs.rm = jest.fn().mockResolvedValue(undefined); + fs.stat = jest.fn().mockResolvedValue({ size: 12345 }); + fs.access = jest.fn().mockResolvedValue(undefined); + + fs.readdir.mockImplementation((dir, options) => { + if (options && options.withFileTypes) { + return Promise.resolve([ + { name: 'file1.js', isDirectory: () => false }, + { name: 'directory', isDirectory: () => true } + ]); + } else { + return Promise.resolve(['file1.js', 'directory']); + } + }); + + const mockZipInstance = { + addLocalFolder: jest.fn(), + addLocalFile: jest.fn(), + writeZip: jest.fn() + }; + + const mockEntries = [ + { + entryName: 'file1.js', + header: { size: 1024 } + }, + { + entryName: 'directory/subfile.js', + header: { size: 2048 } + } + ]; + + AdmZip.mockImplementation((zipPath) => { + if (zipPath) { + + return { + getEntries: jest.fn().mockReturnValue(mockEntries) + }; + } + + return mockZipInstance; + }); + + core.info = jest.fn(); + core.error = jest.fn(); + }); + + test('should successfully package artifacts', async () => { + const artifactsDir = '/mock/artifacts'; + const result = await packageCodeArtifacts(artifactsDir); + + const expectedTempDir = '/mock/tmp/lambda-temp-1234567890'; + const expectedZipPath = '/mock/tmp/lambda-function-1234567890.zip'; + + expect(fs.rm).toHaveBeenCalledWith(expectedTempDir, { recursive: true, force: true }); + expect(fs.mkdir).toHaveBeenCalledWith(expectedTempDir, { recursive: true }); + + expect(fs.readdir).toHaveBeenCalledWith(expect.any(String), expect.any(Object)); + + expect(fs.cp).toHaveBeenCalledTimes(2); + expect(fs.cp).toHaveBeenCalledWith( + expect.stringContaining('file1.js'), + expect.stringContaining(`lambda-temp-${mockTimestamp}/file1.js`), + { recursive: true } + ); + expect(fs.cp).toHaveBeenCalledWith( + expect.stringContaining('directory'), + expect.stringContaining(`lambda-temp-${mockTimestamp}/directory`), + { recursive: true } + ); + + const zipInstance = AdmZip.mock.results[0].value; + expect(zipInstance.addLocalFolder).toHaveBeenCalledWith(`${expectedTempDir}/directory`, 'directory'); + expect(zipInstance.addLocalFile).toHaveBeenCalledWith(`${expectedTempDir}/file1.js`); + expect(zipInstance.writeZip).toHaveBeenCalledWith(expectedZipPath); + + expect(core.info).toHaveBeenCalledWith('Creating ZIP file with standard options'); + + expect(result).toBe(expectedZipPath); + }); + + test('should handle nested directory structures', async () => { + + fs.readdir.mockImplementation((dir, options) => { + if (options && options.withFileTypes) { + return Promise.resolve([ + { name: 'file1.js', isDirectory: () => false }, + { name: 'dir1', isDirectory: () => true } + ]); + } else { + return Promise.resolve(['file1.js', 'dir1']); + } + }); + const artifactsDir = '/mock/artifacts'; + await packageCodeArtifacts(artifactsDir); + + const expectedTempDir = '/mock/tmp/lambda-temp-1234567890'; + + expect(fs.cp).toHaveBeenCalledTimes(2); + expect(fs.cp).toHaveBeenCalledWith( + expect.stringContaining('file1.js'), + expect.stringContaining(`${expectedTempDir}/file1.js`), + { recursive: true } + ); + expect(fs.cp).toHaveBeenCalledWith( + expect.stringContaining('dir1'), + expect.stringContaining(`${expectedTempDir}/dir1`), + { recursive: true } + ); + }); + + test('should handle files with hidden/dot files', async () => { + + fs.readdir.mockImplementation((dir, options) => { + if (options && options.withFileTypes) { + return Promise.resolve([ + { name: 'file1.js', isDirectory: () => false }, + { name: '.env', isDirectory: () => false }, + { name: '.config', isDirectory: () => false } + ]); + } else { + return Promise.resolve(['file1.js', '.env', '.config']); + } + }); + const artifactsDir = '/mock/artifacts'; + await packageCodeArtifacts(artifactsDir); + + const expectedTempDir = '/mock/tmp/lambda-temp-1234567890'; + + expect(fs.cp).toHaveBeenCalledTimes(3); + expect(fs.cp).toHaveBeenCalledWith( + expect.stringContaining('.env'), + expect.stringContaining(`${expectedTempDir}/.env`), + { recursive: true } + ); + expect(fs.cp).toHaveBeenCalledWith( + expect.stringContaining('.config'), + expect.stringContaining(`${expectedTempDir}/.config`), + { recursive: true } + ); + }); + + test('should handle error during directory cleanup', async () => { + + const expectedTempDir = '/mock/tmp/lambda-temp-1234567890'; + const expectedZipPath = '/mock/tmp/lambda-function-1234567890.zip'; + + const rmError = new Error('Failed to remove directory'); + fs.rm.mockRejectedValueOnce(rmError); + + fs.readdir.mockImplementation((dir, options) => { + if (options && options.withFileTypes) { + return Promise.resolve([ + { name: 'file1.js', isDirectory: () => false }, + { name: 'directory', isDirectory: () => true } + ]); + } else { + return Promise.resolve(['file1.js', 'directory']); + } + }); + const artifactsDir = '/mock/artifacts'; + const result = await packageCodeArtifacts(artifactsDir); + + expect(fs.mkdir).toHaveBeenCalled(); + expect(result).toBe(expectedZipPath); + }); + + test('should handle error during directory creation', async () => { + + const mkdirError = new Error('Failed to create directory'); + fs.mkdir.mockRejectedValue(mkdirError); + const artifactsDir = '/mock/artifacts'; + + await expect(packageCodeArtifacts(artifactsDir)).rejects.toThrow('Failed to create directory'); + + expect(core.error).toHaveBeenCalledWith('Failed to package artifacts: Failed to create directory'); + }); + + test('should handle error during file copying', async () => { + + fs.cp.mockImplementation((src, dest, options) => { + if (src.includes('file1.js')) { + return Promise.reject(new Error('Failed to copy file')); + } + return Promise.resolve(); + }); + + fs.readdir.mockImplementation((dir, options) => { + if (options && options.withFileTypes) { + return Promise.resolve([ + { name: 'file1.js', isDirectory: () => false }, + { name: 'directory', isDirectory: () => true } + ]); + } else { + return Promise.resolve(['file1.js', 'directory']); + } + }); + const artifactsDir = '/mock/artifacts'; + + await expect(packageCodeArtifacts(artifactsDir)).rejects.toThrow('Failed to copy file'); + + expect(core.error).toHaveBeenCalledWith('Failed to package artifacts: Failed to copy file'); + }); + + test('should handle error during zip creation', async () => { + + const mockZipInstance = { + addLocalFolder: jest.fn(), + addLocalFile: jest.fn(), + writeZip: jest.fn().mockImplementation(() => { + throw new Error('Failed to write zip'); + }) + }; + AdmZip.mockImplementation(() => mockZipInstance); + + fs.readdir.mockImplementation((dir, options) => { + if (options && options.withFileTypes) { + return Promise.resolve([ + { name: 'file1.js', isDirectory: () => false }, + { name: 'directory', isDirectory: () => true } + ]); + } else { + return Promise.resolve(['file1.js', 'directory']); + } + }); + const artifactsDir = '/mock/artifacts'; + + await expect(packageCodeArtifacts(artifactsDir)).rejects.toThrow('Failed to write zip'); + + expect(core.error).toHaveBeenCalledWith('Failed to package artifacts: Failed to write zip'); + }); + + test('should verify zip file contents after creation', async () => { + const artifactsDir = '/mock/artifacts'; + const zipPath = await packageCodeArtifacts(artifactsDir); + + const verificationZip = new AdmZip(zipPath); + const entries = verificationZip.getEntries(); + + expect(entries).toHaveLength(2); + expect(entries[0].entryName).toBe('file1.js'); + expect(entries[1].entryName).toBe('directory/subfile.js'); + + expect(entries[0].header.size).toBe(1024); + expect(entries[1].header.size).toBe(2048); + + expect(fs.stat).toHaveBeenCalledWith(zipPath); + }); + + test('should use provided artifact directory path correctly', async () => { + + const customArtifactsDir = '/custom/artifacts/path'; + validations.validateAndResolvePath = jest.fn().mockReturnValue(customArtifactsDir); + await packageCodeArtifacts(customArtifactsDir); + + expect(validations.validateAndResolvePath).toHaveBeenCalledWith(customArtifactsDir, '/mock/cwd'); + + expect(fs.access).toHaveBeenCalledWith(customArtifactsDir); + expect(fs.readdir).toHaveBeenCalledWith(customArtifactsDir); + }); + + test('should throw error for empty artifacts directory', async () => { + + fs.readdir.mockImplementation(() => { + return Promise.resolve([]); + }); + + const artifactsDir = '/mock/artifacts'; + const resolvedPath = '/resolved/path'; + validations.validateAndResolvePath = jest.fn().mockReturnValue(resolvedPath); + + await expect(packageCodeArtifacts(artifactsDir)).rejects.toThrow( + `Code artifacts directory '${resolvedPath}' is empty, no files to package` + ); + }); + + test('should handle artifacts directory access error', async () => { + + const accessError = new Error('Directory does not exist'); + fs.access.mockRejectedValueOnce(accessError); + + const artifactsDir = '/mock/artifacts'; + const resolvedPath = '/resolved/path'; + validations.validateAndResolvePath = jest.fn().mockReturnValue(resolvedPath); + await expect(packageCodeArtifacts(artifactsDir)).rejects.toThrow( + `Code artifacts directory '${resolvedPath}' does not exist or is not accessible` + ); + }); + + test('should use validateAndResolvePath to prevent path traversal attacks', async () => { + + validations.validateAndResolvePath = jest.fn().mockReturnValue('/safe/resolved/path'); + const artifactsDir = './artifacts'; + await packageCodeArtifacts(artifactsDir); + + expect(validations.validateAndResolvePath).toHaveBeenCalledWith(artifactsDir, '/mock/cwd'); + + expect(fs.access).toHaveBeenCalledWith('/safe/resolved/path'); + expect(fs.readdir).toHaveBeenCalledWith('/safe/resolved/path'); + }); + + test('should throw error when validateAndResolvePath detects path traversal', async () => { + + const securityError = new Error('Security error: Path traversal attempt detected'); + validations.validateAndResolvePath = jest.fn().mockImplementation(() => { + throw securityError; + }); + const maliciousPath = '../../../etc/passwd'; + + await expect(packageCodeArtifacts(maliciousPath)).rejects.toThrow('Security error: Path traversal attempt detected'); + + expect(validations.validateAndResolvePath).toHaveBeenCalledWith(maliciousPath, '/mock/cwd'); + }); + + test('should handle absolute paths using validateAndResolvePath', async () => { + + path.isAbsolute = jest.fn().mockReturnValue(true); + validations.validateAndResolvePath = jest.fn().mockReturnValue('/absolute/path/artifacts'); + const absolutePath = '/absolute/path/artifacts'; + await packageCodeArtifacts(absolutePath); + + expect(validations.validateAndResolvePath).toHaveBeenCalledWith(absolutePath, '/mock/cwd'); + + expect(fs.access).toHaveBeenCalledWith('/absolute/path/artifacts'); + }); +}); \ No newline at end of file diff --git a/__tests__/s3_bucket_operations.test.js b/__tests__/s3_bucket_operations.test.js new file mode 100644 index 00000000..19215cc5 --- /dev/null +++ b/__tests__/s3_bucket_operations.test.js @@ -0,0 +1,522 @@ +jest.mock('@actions/core'); +jest.mock('@aws-sdk/client-lambda'); +jest.mock('@aws-sdk/client-s3'); +jest.mock('@aws-sdk/client-sts'); +jest.mock('fs/promises'); + +const core = require('@actions/core'); +const { S3Client, HeadBucketCommand, CreateBucketCommand, PutObjectCommand } = require('@aws-sdk/client-s3'); +const { STSClient, GetCallerIdentityCommand } = require('@aws-sdk/client-sts'); +const fs = require('fs/promises'); +const mainModule = require('../index'); + +describe('S3 Bucket Operations Tests', () => { + let mockS3Send; + beforeEach(() => { + jest.clearAllMocks(); + + core.getInput.mockImplementation((name) => { + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': '/mock/artifacts', + 's3-bucket': 'test-lambda-bucket', + 's3-key': 'test-lambda-key.zip' + }; + return inputs[name] || ''; + }); + core.getBooleanInput.mockImplementation((name) => { + if (name === 'create-s3-bucket') return true; + return false; + }); + core.info = jest.fn(); + core.error = jest.fn(); + core.warning = jest.fn(); + core.setFailed = jest.fn(); + core.setOutput = jest.fn(); + + fs.readFile.mockResolvedValue(Buffer.from('mock file content')); + fs.access.mockResolvedValue(undefined); + fs.stat.mockResolvedValue({ + size: 1024 + }); + + mockS3Send = jest.fn(); + S3Client.prototype.send = mockS3Send; + + STSClient.prototype.send = jest.fn().mockImplementation((command) => { + if (command instanceof GetCallerIdentityCommand) { + return Promise.resolve({ + Account: '123456789012' + }); + } + return Promise.reject(new Error('Unknown STS command')); + }); + }); + + describe('checkBucketExists function', () => { + test('should return true when bucket exists', async () => { + + mockS3Send.mockResolvedValueOnce({}); + const s3Client = new S3Client({ region: 'us-east-1' }); + const result = await mainModule.checkBucketExists(s3Client, 'existing-bucket'); + + expect(result).toBe(true); + expect(mockS3Send).toHaveBeenCalledWith(expect.any(HeadBucketCommand)); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('exists')); + }); + test('should return false when bucket does not exist (404)', async () => { + + const notFoundError = new Error('Not Found'); + notFoundError.$metadata = { httpStatusCode: 404 }; + notFoundError.name = 'NotFound'; + + mockS3Send.mockRejectedValueOnce(notFoundError); + const s3Client = new S3Client({ region: 'us-east-1' }); + const result = await mainModule.checkBucketExists(s3Client, 'non-existing-bucket'); + expect(result).toBe(false); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('does not exist')); + }); + test('should throw error when HeadBucket returns non-404 error', async () => { + + const accessError = new Error('Access Denied'); + accessError.$metadata = { httpStatusCode: 403 }; + accessError.name = 'AccessDenied'; + + mockS3Send.mockRejectedValueOnce(accessError); + const s3Client = new S3Client({ region: 'us-east-1' }); + await expect(mainModule.checkBucketExists(s3Client, 'forbidden-bucket')) + .rejects.toThrow(); + expect(core.error).toHaveBeenCalledWith(expect.stringContaining('Error checking if bucket exists')); + }); + test('should handle region mismatch error (301)', async () => { + + const regionError = new Error('Moved Permanently'); + regionError.$metadata = { httpStatusCode: 301 }; + mockS3Send.mockRejectedValueOnce(regionError); + + const s3Client = new S3Client({ region: 'us-east-1' }); + + s3Client.config = { region: 'us-east-1' }; + await expect(mainModule.checkBucketExists(s3Client, 'wrong-region-bucket')) + .rejects.toThrow(/different region/); + expect(core.error).toHaveBeenCalledWith(expect.stringContaining('REGION MISMATCH ERROR')); + }); + }); + + describe('createBucket function', () => { + test('should create bucket in non-us-east-1 regions with LocationConstraint', async () => { + mockS3Send.mockResolvedValueOnce({ + Location: 'http://test-bucket.s3.amazonaws.com/' + }); + const s3Client = new S3Client({ region: 'us-west-2' }); + await mainModule.createBucket(s3Client, 'test-bucket', 'us-west-2'); + + expect(mockS3Send).toHaveBeenCalled(); + expect(CreateBucketCommand).toHaveBeenCalledWith( + expect.objectContaining({ + Bucket: 'test-bucket', + CreateBucketConfiguration: { LocationConstraint: 'us-west-2' } + }) + ); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Successfully created S3 bucket')); + }); + test('should create bucket in us-east-1 without LocationConstraint', async () => { + mockS3Send.mockResolvedValueOnce({ + Location: 'http://test-bucket.s3.amazonaws.com/' + }); + const s3Client = new S3Client({ region: 'us-east-1' }); + await mainModule.createBucket(s3Client, 'test-bucket', 'us-east-1'); + + expect(mockS3Send).toHaveBeenCalled(); + expect(CreateBucketCommand).toHaveBeenCalledWith( + expect.objectContaining({ + Bucket: 'test-bucket' + }) + ); + const createBucketCommand = CreateBucketCommand.mock.calls[0][0]; + expect(createBucketCommand.CreateBucketConfiguration).toBeUndefined(); + }); + test('should handle bucket already exists error', async () => { + const existsError = new Error('Bucket already exists'); + existsError.name = 'BucketAlreadyExists'; + mockS3Send.mockRejectedValueOnce(existsError); + const s3Client = new S3Client({ region: 'us-east-1' }); + await expect(mainModule.createBucket(s3Client, 'existing-bucket', 'us-east-1')) + .rejects.toThrow(); + expect(core.error).toHaveBeenCalledWith(expect.stringContaining('already taken')); + }); + + test('should handle bucket already owned by you error', async () => { + const ownedError = new Error('Bucket already owned by you'); + ownedError.name = 'BucketAlreadyOwnedByYou'; + ownedError.code = 'BucketAlreadyOwnedByYou'; + + mockS3Send.mockRejectedValueOnce(ownedError); + const s3Client = new S3Client({ region: 'us-east-1' }); + + await expect(mainModule.createBucket(s3Client, 'my-existing-bucket', 'us-east-1')) + .rejects.toThrow(ownedError); + + expect(core.info).toHaveBeenCalledWith('Creating S3 bucket: my-existing-bucket'); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Sending CreateBucket request')); + + expect(mockS3Send).toHaveBeenCalledTimes(1); + expect(mockS3Send).toHaveBeenCalledWith(expect.any(CreateBucketCommand)); + }); + test('should handle permission denied error', async () => { + const accessError = new Error('Access Denied'); + accessError.name = 'AccessDenied'; + accessError.$metadata = { httpStatusCode: 403 }; + mockS3Send.mockRejectedValueOnce(accessError); + const s3Client = new S3Client({ region: 'us-east-1' }); + await expect(mainModule.createBucket(s3Client, 'test-bucket', 'us-east-1')) + .rejects.toThrow(/Access denied/); + expect(core.error).toHaveBeenCalledWith(expect.stringContaining('Access denied')); + }); + test('should validate bucket name before creating', async () => { + const s3Client = new S3Client({ region: 'us-east-1' }); + await expect(mainModule.createBucket(s3Client, 'Invalid_Bucket', 'us-east-1')) + .rejects.toThrow(/Invalid bucket name/); + expect(mockS3Send).not.toHaveBeenCalled(); + }); + }); + + describe('validateBucketName function', () => { + test('should validate correct bucket names', () => { + expect(mainModule.validateBucketName('valid-bucket-name')).toBe(true); + expect(mainModule.validateBucketName('my.bucket.name')).toBe(true); + expect(mainModule.validateBucketName('bucket-123')).toBe(true); + expect(mainModule.validateBucketName('a-really-long-but-valid-bucket-name-within-63-chars')).toBe(true); + }); + test('should reject invalid bucket names', () => { + expect(mainModule.validateBucketName('UPPERCASE')).toBe(false); + expect(mainModule.validateBucketName('bucket_with_underscore')).toBe(false); + expect(mainModule.validateBucketName('sh')).toBe(false); + expect(mainModule.validateBucketName('192.168.1.1')).toBe(false); + expect(mainModule.validateBucketName('bucket..name')).toBe(false); + expect(mainModule.validateBucketName('xn--bucket')).toBe(false); + expect(mainModule.validateBucketName('a'.repeat(64))).toBe(false); + expect(mainModule.validateBucketName(null)).toBe(false); + expect(mainModule.validateBucketName(undefined)).toBe(false); + expect(mainModule.validateBucketName(123)).toBe(false); + }); + }); + + describe('uploadToS3 function', () => { + test('should upload file to existing S3 bucket', async () => { + fs.readFile.mockResolvedValue(Buffer.from('test file content')); + jest.spyOn(mainModule, 'checkBucketExists').mockResolvedValue(true); + jest.clearAllMocks(); + + mockS3Send.mockResolvedValueOnce({}); + const result = await mainModule.uploadToS3( + '/path/to/deployment.zip', + 'new-bucket', + 'lambda/function.zip', + 'us-east-1' + ); + expect(result).toEqual({ + bucket: 'new-bucket', + key: 'lambda/function.zip' + }); + expect(mockS3Send).toHaveBeenCalledWith(expect.any(PutObjectCommand)); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('S3 upload successful, file size')); + }); + test('should create bucket if it does not exist', async () => { + + jest.clearAllMocks(); + + fs.readFile.mockResolvedValue(Buffer.from('test file content')); + + const notFoundError = new Error('Not Found'); + notFoundError.$metadata = { httpStatusCode: 404 }; + notFoundError.name = 'NotFound'; + + mockS3Send.mockRejectedValueOnce(notFoundError); + mockS3Send.mockResolvedValueOnce({ + Location: 'http://new-bucket.s3.amazonaws.com/' + }); + + mockS3Send.mockResolvedValueOnce({}); + + jest.spyOn(mainModule, 'createBucket').mockResolvedValue(true); + mockS3Send.mockResolvedValueOnce({}); + try { + await mainModule.uploadToS3( + '/path/to/deployment.zip', + 'new-bucket', + 'lambda/function.zip', + 'us-east-1', + '123456789012' + ); + } catch (error) { + throw error; + } + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Bucket new-bucket does not exist. Attempting to create it')); + expect(mockS3Send).toHaveBeenCalledWith(expect.any(PutObjectCommand)); + }); + test('should handle file access errors', async () => { + + const fileError = new Error('Permission denied'); + fileError.code = 'EACCES'; + + fs.access.mockRejectedValueOnce(fileError); + await expect(mainModule.uploadToS3( + '/inaccessible/file.zip', + 'test-bucket', + 'key.zip', + 'us-east-1', + '123456789012' // Adding expected bucket owner + )).rejects.toThrow('Permission denied'); + expect(core.error).toHaveBeenCalled(); + }); + + test('should handle bucket already owned by you error in uploadToS3', async () => { + fs.readFile.mockResolvedValue(Buffer.from('test file content')); + fs.access.mockResolvedValue(undefined); + + const notFoundError = new Error('Not Found'); + notFoundError.$metadata = { httpStatusCode: 404 }; + notFoundError.name = 'NotFound'; + mockS3Send.mockRejectedValueOnce(notFoundError); + + const ownedError = new Error('Bucket already owned by you'); + ownedError.name = 'BucketAlreadyOwnedByYou'; + ownedError.code = 'BucketAlreadyOwnedByYou'; + + mockS3Send.mockRejectedValueOnce(ownedError); + + await expect(mainModule.uploadToS3( + '/path/to/deployment.zip', + 'my-existing-bucket', + 'lambda/function.zip', + 'us-east-1' + )).rejects.toThrow('Bucket already owned by you'); + + expect(core.error).toHaveBeenCalledWith(expect.stringContaining('Failed to create bucket my-existing-bucket')); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Bucket name my-existing-bucket is already taken')); + + expect(core.info).toHaveBeenCalledWith('Bucket my-existing-bucket does not exist. Attempting to create it...'); + expect(core.info).toHaveBeenCalledWith('Creating S3 bucket: my-existing-bucket'); + }); + test('should handle S3 upload errors', async () => { + + fs.readFile.mockResolvedValue(Buffer.from('test file content')); + jest.spyOn(mainModule, 'checkBucketExists').mockResolvedValue(true); + + const uploadError = new Error('Upload failed'); + uploadError.name = 'S3Error'; + uploadError.$metadata = { httpStatusCode: 500 }; + + mockS3Send.mockRejectedValueOnce(uploadError); + + await expect(mainModule.uploadToS3( + '/path/to/file.zip', + 'test-bucket', + 'key.zip', + 'us-east-1', + '123456789012' + )).rejects.toThrow('Upload failed'); + + const errorCalls = core.error.mock.calls.flat().join(' '); + + expect(errorCalls).toContain('upload'); + expect(errorCalls).toContain('failed'); + }); + test('should handle S3 permission errors', async () => { + fs.readFile.mockResolvedValue(Buffer.from('test file content')); + + jest.spyOn(mainModule, 'checkBucketExists').mockResolvedValue(true); + + const permError = new Error('Access Denied'); + + permError.name = 'AccessDenied'; + permError.$metadata = { httpStatusCode: 403 }; + + mockS3Send.mockRejectedValueOnce(permError); + + await expect(mainModule.uploadToS3( + '/path/to/file.zip', + 'test-bucket', + 'key.zip', + 'us-east-1' + )).rejects.toThrow('Access denied'); + + expect(core.error).toHaveBeenCalledWith(expect.stringContaining('Access denied')); + }); + }); + + describe('getAwsAccountId function', () => { + test('should retrieve AWS account ID successfully', async () => { + const result = await mainModule.getAwsAccountId('us-east-1'); + expect(result).toBe('123456789012'); + expect(STSClient.prototype.send).toHaveBeenCalledWith(expect.any(GetCallerIdentityCommand)); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Successfully retrieved AWS account ID')); + }); + + test('should handle errors and return null', async () => { + STSClient.prototype.send = jest.fn().mockRejectedValueOnce(new Error('STS error')); + const result = await mainModule.getAwsAccountId('us-east-1'); + expect(result).toBeNull(); + expect(core.warning).toHaveBeenCalledWith(expect.stringContaining('Failed to retrieve AWS account ID')); + }); + }); + + describe('S3 Key Generation', () => { + test('should generate S3 key with timestamp and commit hash', () => { + const originalEnv = process.env; + process.env = { + ...originalEnv, + GITHUB_SHA: 'abcdef1234567890' + }; + const key = mainModule.generateS3Key('test-function'); + process.env = originalEnv; + expect(key).toMatch(/^lambda-deployments\/test-function\/[\d-]+-abcdef1.zip$/); + }); + test('should generate S3 key without commit hash if not available', () => { + const originalEnv = process.env; + process.env = { ...originalEnv }; + delete process.env.GITHUB_SHA; + const key = mainModule.generateS3Key('test-function'); + + process.env = originalEnv; + expect(key).toMatch(/^lambda-deployments\/test-function\//); + expect(key).toMatch(/\.zip$/); + expect(key).not.toMatch(/[a-f0-9]{7,}\.zip$/); + }); + }); + + describe('End-to-End S3 Deployment Flow', () => { + let originalRun; + beforeAll(() => { + originalRun = mainModule.run; + }); + afterAll(() => { + mainModule.run = originalRun; + }); + beforeEach(() => { + jest.spyOn(mainModule, 'packageCodeArtifacts').mockResolvedValue('/mock/package.zip'); + jest.spyOn(mainModule, 'checkFunctionExists').mockResolvedValue(true); + jest.spyOn(mainModule, 'hasConfigurationChanged').mockResolvedValue(false); + jest.spyOn(mainModule, 'waitForFunctionUpdated').mockResolvedValue(undefined); + + jest.spyOn(mainModule, 'uploadToS3').mockImplementation(() => { + return Promise.resolve({ + bucket: 'mock-bucket', + key: 'mock-key.zip' + }); + }); + jest.spyOn(mainModule, 'generateS3Key').mockImplementation((functionName) => { + return `lambda-deployments/${functionName}/timestamp-mock.zip`; + }); + + mainModule.run = jest.fn().mockImplementation(() => { + return Promise.resolve(); + }); + }); + test('should use S3 deployment method when s3-bucket is provided', async () => { + core.getInput.mockImplementation((name) => { + const inputs = { + 'function-name': 'test-function', + 'code-artifacts-dir': '/mock/artifacts', + 'region': 'us-east-1', + 's3-bucket': 'lambda-deployment-bucket', + 's3-key': 'custom/key/path.zip' + }; + return inputs[name] || ''; + }); + + mainModule.uploadToS3.mockReset(); + mainModule.uploadToS3.mockResolvedValueOnce({ + bucket: 'lambda-deployment-bucket', + key: 'custom/key/path.zip' + }); + + const testUploadFunction = async () => { + const s3Bucket = core.getInput('s3-bucket'); + const s3Key = core.getInput('s3-key'); + const region = core.getInput('region'); + if (s3Bucket) { + jest.spyOn(mainModule, 'getAwsAccountId').mockResolvedValue('123456789012'); + return mainModule.uploadToS3( + '/mock/package.zip', + s3Bucket, + s3Key, + region + ); + } + return null; + }; + + const result = await testUploadFunction(); + + expect(mainModule.uploadToS3).toHaveBeenCalledWith( + '/mock/package.zip', + 'lambda-deployment-bucket', + 'custom/key/path.zip', + 'us-east-1' + ); + + expect(result).toEqual({ + bucket: 'lambda-deployment-bucket', + key: 'custom/key/path.zip' + }); + }); + test('should generate S3 key when not provided', async () => { + core.getInput.mockImplementation((name) => { + const inputs = { + 'function-name': 'test-function', + 'code-artifacts-dir': '/mock/artifacts', + 'region': 'us-east-1', + 's3-bucket': 'lambda-deployment-bucket' + }; + return inputs[name] || ''; + }); + + mainModule.generateS3Key.mockReset(); + const mockGeneratedKey = 'lambda-deployments/test-function/timestamp.zip'; + mainModule.generateS3Key.mockReturnValueOnce(mockGeneratedKey); + + const testKeyGeneration = () => { + const functionName = core.getInput('function-name'); + const s3Key = core.getInput('s3-key'); + if (!s3Key) { + return mainModule.generateS3Key(functionName); + } + return s3Key; + }; + const result = testKeyGeneration(); + expect(mainModule.generateS3Key).toHaveBeenCalledWith('test-function'); + expect(result).toEqual(mockGeneratedKey); + }); + test('should use ZipFile method if no S3 bucket is provided', async () => { + core.getInput.mockImplementation((name) => { + const inputs = { + 'function-name': 'test-function', + 'code-artifacts-dir': '/mock/artifacts', + 'region': 'us-east-1' + }; + return inputs[name] || ''; + }); + + const testDirectUpload = () => { + const s3Bucket = core.getInput('s3-bucket'); + return !s3Bucket; + }; + + const useDirectUpload = testDirectUpload(); + + expect(useDirectUpload).toBe(true); + + mainModule.uploadToS3.mockReset(); + + const functionName = core.getInput('function-name'); + const s3Bucket = core.getInput('s3-bucket'); + if (s3Bucket) { + await mainModule.uploadToS3('file.zip', s3Bucket, 'key.zip', 'region', '123456789012'); // Adding expected bucket owner + } + expect(mainModule.uploadToS3).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/__tests__/update_function_code.test.js b/__tests__/update_function_code.test.js new file mode 100644 index 00000000..88b9d65b --- /dev/null +++ b/__tests__/update_function_code.test.js @@ -0,0 +1,479 @@ +jest.mock('@actions/core'); +jest.mock('@aws-sdk/client-lambda'); +jest.mock('fs/promises', () => ({ + mkdir: jest.fn().mockResolvedValue(undefined), + stat: jest.fn().mockImplementation(async (path) => ({ + isDirectory: () => path.includes('directory') + })), + copyFile: jest.fn().mockResolvedValue(undefined), + readFile: jest.fn().mockResolvedValue(Buffer.from('mock file content')) +})); +jest.mock('path'); + +const core = require('@actions/core'); +const { + LambdaClient, + UpdateFunctionCodeCommand, + GetFunctionCommand, + GetFunctionConfigurationCommand +} = require('@aws-sdk/client-lambda'); +const fs = require('fs/promises'); +const path = require('path'); +const index = require('../index'); + +describe('Lambda Function Code Tests', () => { + + jest.setTimeout(60); + beforeEach(() => { + jest.resetAllMocks(); + + process.cwd = jest.fn().mockReturnValue('/mock/cwd'); + + path.join.mockImplementation((...parts) => parts.join('/')); + + core.getInput.mockImplementation((name) => { + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': '/mock/src', + 'architectures': 'x86_64' + }; + return inputs[name] || ''; + }); + core.getBooleanInput.mockImplementation((name) => { + if (name === 'dry-run') return false; + if (name === 'publish') return true; + return false; + }); + core.info.mockImplementation(() => {}); + core.error.mockImplementation(() => {}); + core.setFailed.mockImplementation(() => {}); + core.setOutput.mockImplementation(() => {}); + core.debug.mockImplementation(() => {}); + + const mockFunctionResponse = { + Configuration: { + FunctionName: 'test-function', + FunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:test-function', + Runtime: 'nodejs18.x', + Handler: 'index.handler' + } + }; + const mockUpdateCodeResponse = { + FunctionName: 'test-function', + FunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:test-function', + Version: '2', + LastUpdateStatus: 'Successful' + }; + + LambdaClient.prototype.send = jest.fn().mockImplementation((command) => { + if (command instanceof GetFunctionCommand) { + return Promise.resolve(mockFunctionResponse); + } else if (command instanceof GetFunctionConfigurationCommand) { + return Promise.resolve(mockFunctionResponse.Configuration); + } else if (command instanceof UpdateFunctionCodeCommand) { + return Promise.resolve(mockUpdateCodeResponse); + } + return Promise.resolve({}); + }); + + jest.spyOn(index, 'checkBucketExists').mockResolvedValue(true); + + jest.spyOn(index, 'checkFunctionExists').mockResolvedValue(true); + }); + test('Test the updateFunctionCode function with S3 upload method', async () => { + const originalUpdateFunctionCode = index.updateFunctionCode; + + index.updateFunctionCode = jest.fn().mockImplementation(async (client, params) => { + const s3Result = { + bucket: params.s3Bucket, + key: params.s3Key + }; + + const command = new UpdateFunctionCodeCommand({ + FunctionName: params.functionName, + S3Bucket: params.s3Bucket, + S3Key: params.s3Key, + ...(params.architectures && { + Architectures: Array.isArray(params.architectures) + ? params.architectures + : [params.architectures] + }), + ...(params.publish !== undefined && { Publish: params.publish }), + ...(params.dryRun !== undefined && { DryRun: params.dryRun }) + }); + + const response = await client.send(command); + + core.setOutput('function-arn', response.FunctionArn); + if (response.Version) { + core.setOutput('version', response.Version); + } + + return response; + }); + + try { + const mockClient = new LambdaClient(); + const mockParams = { + functionName: 'test-function', + finalZipPath: '/mock/path/lambda.zip', + useS3Method: true, + s3Bucket: 'test-bucket', + s3Key: 'test-key', + architectures: 'x86_64', + publish: true, + dryRun: false, + region: 'us-east-1' + }; + + await index.updateFunctionCode(mockClient, mockParams); + + expect(index.updateFunctionCode).toHaveBeenCalledWith(mockClient, mockParams); + + const command = UpdateFunctionCodeCommand.mock.calls[0][0]; + expect(command).toHaveProperty('FunctionName', 'test-function'); + expect(command).toHaveProperty('S3Bucket', 'test-bucket'); + expect(command).toHaveProperty('S3Key', 'test-key'); + expect(command).toHaveProperty('Architectures', ['x86_64']); + expect(command).toHaveProperty('Publish', true); + + expect(core.setOutput).toHaveBeenCalledWith('function-arn', expect.any(String)); + expect(core.setOutput).toHaveBeenCalledWith('version', expect.any(String)); + } finally { + index.updateFunctionCode = originalUpdateFunctionCode; + } + }); + + test('S3 method should propagate errors from uploadToS3', async () => { + const originalUpdateFunctionCode = index.updateFunctionCode; + + try { + index.updateFunctionCode = async (client, params) => { + if (params.useS3Method) { + core.info(`Using S3 deployment method with bucket: ${params.s3Bucket}, key: ${params.s3Key}`); + + await index.uploadToS3(params.finalZipPath, params.s3Bucket, params.s3Key, params.region); + + core.info(`Successfully uploaded package to S3: s3://${params.s3Bucket}/${params.s3Key}`); + + } else { + return; + } + }; + + core.info = jest.fn(); + + const mockClient = {}; + + const mockParams = { + functionName: 'test-function', + finalZipPath: '/mock/path/lambda.zip', + useS3Method: true, + s3Bucket: 'test-bucket', + s3Key: 'test-key', + architectures: 'x86_64', + publish: true, + dryRun: false, + region: 'us-east-1' + }; + + const originalUploadToS3 = index.uploadToS3; + const testError = new Error('S3 upload failure'); + index.uploadToS3 = jest.fn().mockRejectedValue(testError); + + await expect(index.updateFunctionCode(mockClient, mockParams)) + .rejects.toThrow('S3 upload failure'); + + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining(`Using S3 deployment method with bucket: ${mockParams.s3Bucket}, key: ${mockParams.s3Key}`) + ); + + expect(index.uploadToS3).toHaveBeenCalledWith( + mockParams.finalZipPath, + mockParams.s3Bucket, + mockParams.s3Key, + mockParams.region + ); + + expect(core.info).not.toHaveBeenCalledWith( + expect.stringContaining('Successfully uploaded package to S3') + ); + + index.uploadToS3 = originalUploadToS3; + } finally { + index.updateFunctionCode = originalUpdateFunctionCode; + } + }); + + test('Handle errors in updateFunctionCode function', async () => { + const mockClient = new LambdaClient(); + const mockError = new Error('Function code update failed'); + mockError.name = 'ResourceNotFoundException'; + mockClient.send.mockRejectedValue(mockError); + + const mockParams = { + functionName: 'test-function', + finalZipPath: '/mock/path/lambda.zip', + useS3Method: false, + architectures: 'x86_64', + publish: true, + dryRun: false, + region: 'us-east-1' + }; + + await expect(index.updateFunctionCode(mockClient, mockParams)) + .rejects.toThrow(); + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('Failed to update function code') + ); + }); + + test('Test direct upload method (ZipFile parameter)', async () => { + const mockZipContent = Buffer.from('mock zip content'); + fs.readFile.mockResolvedValue(mockZipContent); + + const mockClient = new LambdaClient(); + + mockClient.send.mockResolvedValue({ + FunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:test-function', + Version: '3' + }); + + const mockParams = { + functionName: 'test-function', + finalZipPath: '/mock/path/lambda.zip', + useS3Method: false, + architectures: 'x86_64', + publish: true, + dryRun: false, + region: 'us-east-1' + }; + + await index.updateFunctionCode(mockClient, mockParams); + + expect(fs.readFile).toHaveBeenCalledWith(mockParams.finalZipPath); + + expect(mockClient.send).toHaveBeenCalled(); + const commandCall = mockClient.send.mock.calls[0][0]; + expect(commandCall).toBeInstanceOf(UpdateFunctionCodeCommand); + + expect(core.info).toHaveBeenCalledWith(expect.stringContaining(`Original buffer length: ${mockZipContent.length} bytes`)); + + expect(core.setOutput).toHaveBeenCalledWith('function-arn', expect.any(String)); + expect(core.setOutput).toHaveBeenCalledWith('version', expect.any(String)); + }); + + test('Handle file read errors - ENOENT (file not found)', async () => { + const readError = new Error('File not found'); + readError.code = 'ENOENT'; + fs.readFile.mockRejectedValue(readError); + + const mockClient = new LambdaClient(); + + const mockParams = { + functionName: 'test-function', + finalZipPath: '/mock/path/lambda.zip', + useS3Method: false, + codeArtifactsDir: '/mock/src', + architectures: 'x86_64', + publish: true, + dryRun: false, + region: 'us-east-1' + }; + + await expect(index.updateFunctionCode(mockClient, mockParams)) + .rejects.toThrow('File not found'); + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('Failed to read Lambda deployment package') + ); + + expect(core.error).toHaveBeenCalledWith( + expect.stringContaining('File not found. Ensure the code artifacts directory') + ); + }); + + test('Handle file read errors - EACCES (permission denied)', async () => { + const readError = new Error('Permission denied'); + readError.code = 'EACCES'; + fs.readFile.mockRejectedValue(readError); + + const mockClient = new LambdaClient(); + + const mockParams = { + functionName: 'test-function', + finalZipPath: '/mock/path/lambda.zip', + useS3Method: false, + codeArtifactsDir: '/mock/src', + architectures: 'x86_64', + publish: true, + dryRun: false, + region: 'us-east-1' + }; + + await expect(index.updateFunctionCode(mockClient, mockParams)) + .rejects.toThrow('Permission denied'); + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('Failed to read Lambda deployment package') + ); + + expect(core.error).toHaveBeenCalledWith( + expect.stringContaining('Permission denied. Check file access permissions.') + ); + }); + + test('Test dry run mode', async () => { + const mockZipContent = Buffer.from('mock zip content'); + fs.readFile.mockResolvedValue(mockZipContent); + + const mockClient = new LambdaClient(); + + mockClient.send.mockResolvedValue({ + FunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:test-function', + Version: '$LATEST' + }); + + const mockParams = { + functionName: 'test-function', + finalZipPath: '/mock/path/lambda.zip', + useS3Method: false, + architectures: 'x86_64', + publish: true, + dryRun: true, + region: 'us-east-1' + }; + + await index.updateFunctionCode(mockClient, mockParams); + + expect(mockClient.send).toHaveBeenCalled(); + const commandCall = mockClient.send.mock.calls[0][0]; + expect(commandCall).toBeInstanceOf(UpdateFunctionCodeCommand); + + expect(core.info).toHaveBeenCalledWith(expect.stringMatching(/\[DRY RUN\]/)); + expect(core.info).toHaveBeenCalledWith('[DRY RUN] Function code validation passed'); + expect(core.info).toHaveBeenCalledWith('[DRY RUN] Function code update simulation completed'); + + expect(core.setOutput).toHaveBeenCalledWith('function-arn', expect.any(String)); + expect(core.setOutput).toHaveBeenCalledWith('version', expect.any(String)); + }); + + test('Handle AWS specific errors - ThrottlingException', async () => { + + const mockZipContent = Buffer.from('mock zip content'); + fs.readFile.mockResolvedValue(mockZipContent); + + const mockClient = new LambdaClient(); + + const throttlingError = new Error('Rate exceeded'); + throttlingError.name = 'ThrottlingException'; + mockClient.send.mockRejectedValue(throttlingError); + + const mockParams = { + functionName: 'test-function', + finalZipPath: '/mock/path/lambda.zip', + useS3Method: false, + architectures: 'x86_64', + publish: true, + dryRun: false, + region: 'us-east-1' + }; + + await expect(index.updateFunctionCode(mockClient, mockParams)) + .rejects.toThrow('Rate exceeded'); + + expect(core.setFailed).toHaveBeenCalledWith( + 'Rate limit exceeded and maximum retries reached: Rate exceeded' + ); + }); + + test('Handle AWS specific errors - Server error (500)', async () => { + const mockZipContent = Buffer.from('mock zip content'); + fs.readFile.mockResolvedValue(mockZipContent); + + const mockClient = new LambdaClient(); + + const serverError = new Error('Internal server error'); + serverError.$metadata = { httpStatusCode: 500 }; + mockClient.send.mockRejectedValue(serverError); + + const mockParams = { + functionName: 'test-function', + finalZipPath: '/mock/path/lambda.zip', + useS3Method: false, + architectures: 'x86_64', + publish: true, + dryRun: false, + region: 'us-east-1' + }; + + await expect(index.updateFunctionCode(mockClient, mockParams)) + .rejects.toThrow('Internal server error'); + + expect(core.setFailed).toHaveBeenCalledWith( + 'Server error (500): Internal server error. All retry attempts failed.' + ); + }); + + test('Handle AWS specific errors - Access denied', async () => { + const mockZipContent = Buffer.from('mock zip content'); + fs.readFile.mockResolvedValue(mockZipContent); + + const mockClient = new LambdaClient(); + + const accessError = new Error('Access denied'); + accessError.name = 'AccessDeniedException'; + mockClient.send.mockRejectedValue(accessError); + + const mockParams = { + functionName: 'test-function', + finalZipPath: '/mock/path/lambda.zip', + useS3Method: false, + architectures: 'x86_64', + publish: true, + dryRun: false, + region: 'us-east-1' + }; + + await expect(index.updateFunctionCode(mockClient, mockParams)) + .rejects.toThrow('Access denied'); + + expect(core.setFailed).toHaveBeenCalledWith( + 'Action failed with error: Permissions error: Access denied. Check IAM roles.' + ); + }); + + test('Log stack trace when available', async () => { + const mockZipContent = Buffer.from('mock zip content'); + fs.readFile.mockResolvedValue(mockZipContent); + + const mockClient = new LambdaClient(); + + const error = new Error('Something went wrong'); + error.stack = 'Error: Something went wrong\n at Function.updateFunctionCode'; + mockClient.send.mockRejectedValue(error); + + core.debug = jest.fn(); + + const mockParams = { + functionName: 'test-function', + finalZipPath: '/mock/path/lambda.zip', + useS3Method: false, + architectures: 'x86_64', + publish: true, + dryRun: false, + region: 'us-east-1' + }; + + await expect(index.updateFunctionCode(mockClient, mockParams)) + .rejects.toThrow('Something went wrong'); + + expect(core.setFailed).toHaveBeenCalledWith( + 'Failed to update function code: Something went wrong' + ); + + expect(core.debug).toHaveBeenCalledWith(error.stack); + }); +}); diff --git a/__tests__/update_function_config.test.js b/__tests__/update_function_config.test.js new file mode 100644 index 00000000..ef39376b --- /dev/null +++ b/__tests__/update_function_config.test.js @@ -0,0 +1,346 @@ +const { updateFunctionConfiguration } = require('../index'); +const core = require('@actions/core'); +const { UpdateFunctionConfigurationCommand, waitUntilFunctionUpdated } = require('@aws-sdk/client-lambda'); + +jest.mock('@actions/core'); +jest.mock('@aws-sdk/client-lambda', () => { + return { + UpdateFunctionConfigurationCommand: jest.fn(), + waitUntilFunctionUpdated: jest.fn() + }; +}); + +describe('Update Function Configuration Tests', () => { + let mockLambdaClient; + let mockSend; + beforeEach(() => { + jest.clearAllMocks(); + + core.info = jest.fn(); + core.error = jest.fn(); + core.warning = jest.fn(); + core.setFailed = jest.fn(); + + mockSend = jest.fn(); + mockLambdaClient = { + send: mockSend + }; + + waitUntilFunctionUpdated.mockReset(); + waitUntilFunctionUpdated.mockResolvedValue({}); + }); + test('should update function configuration with correct parameters', async () => { + + mockSend.mockResolvedValue({ + FunctionName: 'test-function', + LastUpdateStatus: 'Successful' + }); + + const params = { + functionName: 'test-function', + role: 'arn:aws:iam::123456789012:role/test-role', + handler: 'index.handler', + functionDescription: 'Test function description', + parsedMemorySize: 512, + timeout: 30, + runtime: 'nodejs18.x', + kmsKeyArn: 'arn:aws:kms:us-east-1:123456789012:key/test-key', + ephemeralStorage: 2048, + parsedEnvironment: { TEST_VAR: 'test-value' }, + parsedVpcConfig: { + SubnetIds: ['subnet-123', 'subnet-456'], + SecurityGroupIds: ['sg-123'] + }, + parsedDeadLetterConfig: { + TargetArn: 'arn:aws:sqs:us-east-1:123456789012:test-queue' + }, + parsedTracingConfig: { + Mode: 'Active' + }, + parsedLayers: [ + 'arn:aws:lambda:us-east-1:123456789012:layer:test-layer:1' + ], + parsedFileSystemConfigs: [ + { + Arn: 'arn:aws:elasticfilesystem:us-east-1:123456789012:access-point/fsap-123', + LocalMountPath: '/mnt/efs' + } + ], + parsedImageConfig: { + Command: ['handler'], + EntryPoint: ['/bin/sh'], + WorkingDirectory: '/app' + }, + parsedSnapStart: { + ApplyOn: 'PublishedVersions' + }, + parsedLoggingConfig: { + LogFormat: 'JSON', + ApplicationLogLevel: 'INFO', + SystemLogLevel: 'INFO' + } + }; + + await updateFunctionConfiguration(mockLambdaClient, params); + + const actualParams = UpdateFunctionConfigurationCommand.mock.calls[0][0]; + + expect(actualParams).toHaveProperty('FunctionName', 'test-function'); + expect(actualParams).toHaveProperty('Role', 'arn:aws:iam::123456789012:role/test-role'); + expect(actualParams).toHaveProperty('Handler', 'index.handler'); + expect(actualParams).toHaveProperty('Description', 'Test function description'); + expect(actualParams).toHaveProperty('MemorySize', 512); + expect(actualParams).toHaveProperty('Timeout', 30); + expect(actualParams).toHaveProperty('Runtime', 'nodejs18.x'); + expect(actualParams).toHaveProperty('KMSKeyArn', 'arn:aws:kms:us-east-1:123456789012:key/test-key'); + expect(actualParams).toHaveProperty('EphemeralStorage', { Size: 2048 }); + expect(actualParams).toHaveProperty('Environment', { Variables: { TEST_VAR: 'test-value' } }); + + + if (actualParams.VpcConfig) { + expect(actualParams.VpcConfig).toEqual({ + SubnetIds: ['subnet-123', 'subnet-456'], + SecurityGroupIds: ['sg-123'] + }); + } + if (actualParams.DeadLetterConfig) { + expect(actualParams.DeadLetterConfig).toEqual({ + TargetArn: 'arn:aws:sqs:us-east-1:123456789012:test-queue' + }); + } + if (actualParams.TracingConfig) { + expect(actualParams.TracingConfig).toEqual({ + Mode: 'Active' + }); + } + if (actualParams.Layers) { + expect(actualParams.Layers).toEqual([ + 'arn:aws:lambda:us-east-1:123456789012:layer:test-layer:1' + ]); + } + if (actualParams.FileSystemConfigs) { + expect(actualParams.FileSystemConfigs).toEqual([ + { + Arn: 'arn:aws:elasticfilesystem:us-east-1:123456789012:access-point/fsap-123', + LocalMountPath: '/mnt/efs' + } + ]); + } + if (actualParams.ImageConfig) { + expect(actualParams.ImageConfig).toEqual({ + Command: ['handler'], + EntryPoint: ['/bin/sh'], + WorkingDirectory: '/app' + }); + } + if (actualParams.SnapStart) { + expect(actualParams.SnapStart).toEqual({ + ApplyOn: 'PublishedVersions' + }); + } + if (actualParams.LoggingConfig) { + expect(actualParams.LoggingConfig).toEqual({ + LogFormat: 'JSON', + ApplicationLogLevel: 'INFO', + SystemLogLevel: 'INFO' + }); + } + + expect(mockSend).toHaveBeenCalled(); + + expect(waitUntilFunctionUpdated).toHaveBeenCalled(); + + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Updating function configuration for test-function')); + }); + test('should handle minimal configuration parameters', async () => { + + mockSend.mockResolvedValue({ + FunctionName: 'test-function', + LastUpdateStatus: 'Successful' + }); + + const params = { + functionName: 'test-function', + parsedEnvironment: {} + }; + + await updateFunctionConfiguration(mockLambdaClient, params); + + expect(UpdateFunctionConfigurationCommand).toHaveBeenCalledWith(expect.objectContaining({ + FunctionName: 'test-function', + Environment: { Variables: {} } + })); + + const command = UpdateFunctionConfigurationCommand.mock.calls[0][0]; + expect(command.Role).toBeUndefined(); + expect(command.Handler).toBeUndefined(); + expect(command.Description).toBeUndefined(); + expect(command.MemorySize).toBeUndefined(); + expect(command.Timeout).toBeUndefined(); + expect(command.Runtime).toBeUndefined(); + expect(command.KMSKeyArn).toBeUndefined(); + expect(command.EphemeralStorage).toBeUndefined(); + expect(command.VpcConfig).toBeUndefined(); + expect(command.DeadLetterConfig).toBeUndefined(); + expect(command.TracingConfig).toBeUndefined(); + expect(command.Layers).toBeUndefined(); + expect(command.FileSystemConfigs).toBeUndefined(); + expect(command.ImageConfig).toBeUndefined(); + expect(command.SnapStart).toBeUndefined(); + expect(command.LoggingConfig).toBeUndefined(); + + expect(mockSend).toHaveBeenCalled(); + + expect(waitUntilFunctionUpdated).toHaveBeenCalled(); + }); + test('should handle rate limit errors', async () => { + + const throttlingError = new Error('Rate exceeded'); + throttlingError.name = 'ThrottlingException'; + mockSend.mockRejectedValue(throttlingError); + + const params = { + functionName: 'test-function', + parsedEnvironment: { TEST_VAR: 'test-value' } + }; + + await expect(updateFunctionConfiguration(mockLambdaClient, params)) + .rejects.toThrow('Rate exceeded'); + + expect(core.setFailed).toHaveBeenCalledWith( + 'Rate limit exceeded and maximum retries reached: Rate exceeded' + ); + }); + test('should handle server errors', async () => { + + const serverError = new Error('Internal server error'); + serverError.$metadata = { httpStatusCode: 500 }; + mockSend.mockRejectedValue(serverError); + + const params = { + functionName: 'test-function', + parsedEnvironment: { TEST_VAR: 'test-value' } + }; + + await expect(updateFunctionConfiguration(mockLambdaClient, params)) + .rejects.toThrow('Internal server error'); + + expect(core.setFailed).toHaveBeenCalledWith( + 'Server error (500): Internal server error. All retry attempts failed.' + ); + }); + test('should handle permission errors', async () => { + + const accessError = new Error('Access denied'); + accessError.name = 'AccessDeniedException'; + mockSend.mockRejectedValue(accessError); + + const params = { + functionName: 'test-function', + parsedEnvironment: { TEST_VAR: 'test-value' } + }; + + await expect(updateFunctionConfiguration(mockLambdaClient, params)) + .rejects.toThrow('Access denied'); + + expect(core.setFailed).toHaveBeenCalledWith( + 'Action failed with error: Permissions error: Access denied. Check IAM roles.' + ); + }); + test('should handle general errors', async () => { + + const generalError = new Error('Something went wrong'); + mockSend.mockRejectedValue(generalError); + + const params = { + functionName: 'test-function', + parsedEnvironment: { TEST_VAR: 'test-value' } + }; + + await expect(updateFunctionConfiguration(mockLambdaClient, params)) + .rejects.toThrow('Something went wrong'); + + expect(core.setFailed).toHaveBeenCalledWith( + 'Failed to update function configuration: Something went wrong' + ); + }); + test('should log stack trace when available', async () => { + + const error = new Error('Something went wrong'); + error.stack = 'Error: Something went wrong\n at Function.updateFunctionConfiguration'; + mockSend.mockRejectedValue(error); + + const params = { + functionName: 'test-function', + parsedEnvironment: { TEST_VAR: 'test-value' } + }; + + core.debug = jest.fn(); + + await expect(updateFunctionConfiguration(mockLambdaClient, params)) + .rejects.toThrow('Something went wrong'); + + expect(core.debug).toHaveBeenCalledWith(error.stack); + }); + test('should include all configuration options in the command', async () => { + + mockSend.mockResolvedValue({ + FunctionName: 'test-function' + }); + + const params = { + functionName: 'test-function', + role: null, + handler: undefined, + functionDescription: 'Test description', + parsedMemorySize: 0, + timeout: undefined, + runtime: null, + kmsKeyArn: undefined, + ephemeralStorage: null, + vpcConfig: undefined, + parsedEnvironment: { TEST_VAR: 'test-value' }, + deadLetterConfig: null, + tracingConfig: undefined, + layers: null, + fileSystemConfigs: undefined, + imageConfig: null, + snapStart: undefined, + loggingConfig: null, + + parsedVpcConfig: null, + parsedDeadLetterConfig: undefined, + parsedTracingConfig: null, + parsedLayers: undefined, + parsedFileSystemConfigs: null, + parsedImageConfig: undefined, + parsedSnapStart: null, + parsedLoggingConfig: undefined + }; + + await updateFunctionConfiguration(mockLambdaClient, params); + + expect(UpdateFunctionConfigurationCommand).toHaveBeenCalledWith(expect.objectContaining({ + FunctionName: 'test-function', + Description: 'Test description', + Environment: { Variables: { TEST_VAR: 'test-value' } } + })); + + const command = UpdateFunctionConfigurationCommand.mock.calls[0][0]; + expect(command.Role).toBeUndefined(); + expect(command.Handler).toBeUndefined(); + expect(command.MemorySize).toBeUndefined(); + expect(command.Timeout).toBeUndefined(); + expect(command.Runtime).toBeUndefined(); + expect(command.KMSKeyArn).toBeUndefined(); + expect(command.EphemeralStorage).toBeUndefined(); + expect(command.VpcConfig).toBeUndefined(); + expect(command.DeadLetterConfig).toBeUndefined(); + expect(command.TracingConfig).toBeUndefined(); + expect(command.Layers).toBeUndefined(); + expect(command.FileSystemConfigs).toBeUndefined(); + expect(command.ImageConfig).toBeUndefined(); + expect(command.SnapStart).toBeUndefined(); + expect(command.LoggingConfig).toBeUndefined(); + }); +}); diff --git a/__tests__/validations.test.js b/__tests__/validations.test.js new file mode 100644 index 00000000..318977c2 --- /dev/null +++ b/__tests__/validations.test.js @@ -0,0 +1,1259 @@ +const core = require('@actions/core'); +const { validateAndResolvePath } = require('../validations'); +const originalValidations = jest.requireActual('../validations'); + +jest.mock('@actions/core'); +jest.mock('../validations', () => { + return { + + ...jest.requireActual('../validations'), + + validateAllInputs: jest.fn() + }; +}); + +describe('Validations Tests', () => { + describe('Numeric Input Validation Tests', () => { + describe('Memory Size Validation', () => { + test('should accept valid memory sizes', () => { + + const validSizes = ['128', '256', '512', '1024', '10240']; + for (const size of validSizes) { + jest.clearAllMocks(); + core.getInput.mockImplementation((name) => { + if (name === 'memory-size') return size; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + if (name === 'handler') return 'index.handler'; + if (name === 'runtime') return 'nodejs18.x'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + expect(result.parsedMemorySize).toBe(parseInt(size)); + expect(core.setFailed).not.toHaveBeenCalled(); + } + }); + test('should handle empty memory size input', () => { + jest.clearAllMocks(); + core.getInput.mockImplementation((name) => { + if (name === 'memory-size') return ''; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + if (name === 'handler') return 'index.handler'; + if (name === 'runtime') return 'nodejs18.x'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + expect(result.parsedMemorySize).toBeUndefined(); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + test('should handle non-numeric memory size input', () => { + jest.clearAllMocks(); + core.getInput.mockImplementation((name) => { + if (name === 'memory-size') return 'hello'; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + if (name === 'handler') return 'index.handler'; + if (name === 'runtime') return 'nodejs18.x'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith('Memory size must be a number, got: hello'); + }); + }); + describe('Timeout Validation', () => { + test('should accept valid timeout values', () => { + + const validTimeouts = ['1', '30', '300', '900']; + for (const timeout of validTimeouts) { + jest.clearAllMocks(); + core.getInput.mockImplementation((name) => { + if (name === 'timeout') return timeout; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + if (name === 'handler') return 'index.handler'; + if (name === 'runtime') return 'nodejs18.x'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + expect(result.timeout).toBe(parseInt(timeout)); + expect(core.setFailed).not.toHaveBeenCalled(); + } + }); + test('should handle non-numeric memory size input', () => { + jest.clearAllMocks(); + core.getInput.mockImplementation((name) => { + if (name === 'timeout') return 'hello'; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + if (name === 'handler') return 'index.handler'; + if (name === 'runtime') return 'nodejs18.x'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith('Timeout must be a number, got: hello'); + }); + }); + describe('Ephemeral Storage Validation', () => { + test('should accept valid ephemeral storage values', () => { + + const validStorageValues = ['512', '1024', '2048', '10240']; + for (const storage of validStorageValues) { + jest.clearAllMocks(); + core.getInput.mockImplementation((name) => { + if (name === 'ephemeral-storage') return storage; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + if (name === 'handler') return 'index.handler'; + if (name === 'runtime') return 'nodejs18.x'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + expect(result.ephemeralStorage).toBe(parseInt(storage)); + expect(core.setFailed).not.toHaveBeenCalled(); + } + }); + test('should handle non-numeric memory size input', () => { + jest.clearAllMocks(); + core.getInput.mockImplementation((name) => { + if (name === 'ephemeral-storage') return 'hello'; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + if (name === 'handler') return 'index.handler'; + if (name === 'runtime') return 'nodejs18.x'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith('Ephemeral storage must be a number, got: hello'); + }); + }); + }); + describe('Required Input Validation Tests', () => { + describe('Function Name Validation', () => { + test('should accept valid function names', () => { + const validNames = ['my-function', 'my_function', 'my.function', 'my-function-123']; + for (const name of validNames) { + jest.clearAllMocks(); + core.getInput.mockImplementation((inputName) => { + if (inputName === 'function-name') return name; + if (inputName === 'region') return 'us-east-1'; + if (inputName === 'code-artifacts-dir') return './artifacts'; + if (inputName === 'handler') return 'index.handler'; + if (inputName === 'runtime') return 'nodejs18.x'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + expect(result.functionName).toBe(name); + expect(core.setFailed).not.toHaveBeenCalled(); + } + }); + test('should reject empty function names', () => { + jest.clearAllMocks(); + core.getInput.mockImplementation((inputName) => { + if (inputName === 'function-name') return ''; + if (inputName === 'region') return 'us-east-1'; + if (inputName === 'code-artifacts-dir') return './artifacts'; + if (inputName === 'handler') return 'index.handler'; + if (inputName === 'runtime') return 'nodejs18.x'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith('Function name must be provided'); + }); + }); + describe('Code Artifacts Directory Validation', () => { + test('should accept valid code artifacts directories', () => { + const validDirs = ['./artifacts', '../artifacts', '/home/user/artifacts']; + for (const dir of validDirs) { + jest.clearAllMocks(); + core.getInput.mockImplementation((inputName) => { + if (inputName === 'function-name') return 'test-function'; + if (inputName === 'region') return 'us-east-1'; + if (inputName === 'code-artifacts-dir') return dir; + if (inputName === 'handler') return 'index.handler'; + if (inputName === 'runtime') return 'nodejs18.x'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + expect(result.codeArtifactsDir).toBe(dir); + expect(core.setFailed).not.toHaveBeenCalled(); + } + }); + test('should reject empty code artifacts directories', () => { + jest.clearAllMocks(); + core.getInput.mockImplementation((inputName) => { + if (inputName === 'function-name') return 'test-function'; + if (inputName === 'region') return 'us-east-1'; + if (inputName === 'code-artifacts-dir') return ''; + if (inputName === 'handler') return 'index.handler'; + if (inputName === 'runtime') return 'nodejs18.x'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith('Code-artifacts-dir must be provided'); + }); + }); + describe('Handler Validation', () => { + test('should accept valid handlers', () => { + const validHandlers = ['index.handler', 'my-function.handler', 'my.function.handler']; + for (const handler of validHandlers) { + jest.clearAllMocks(); + core.getInput.mockImplementation((inputName) => { + if (inputName === 'function-name') return 'test-function'; + if (inputName === 'region') return 'us-east-1'; + if (inputName === 'code-artifacts-dir') return './artifacts'; + if (inputName === 'handler') return handler; + if (inputName === 'runtime') return 'nodejs18.x'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + expect(result.handler).toBe(handler); + expect(core.setFailed).not.toHaveBeenCalled(); + } + }); + test('default to index.handler', () => { + jest.clearAllMocks(); + core.getInput.mockImplementation((inputName) => { + if (inputName === 'function-name') return 'test-function'; + if (inputName === 'region') return 'us-east-1'; + if (inputName === 'code-artifacts-dir') return './artifacts'; + if (inputName === 'handler') return ''; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + expect(result.handler).toBe('index.handler'); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + }); + describe('Runtime Validation', () => { + test('should accept valid runtimes', () => { + const validRuntimes = ['nodejs18.x', 'nodejs16.x', 'nodejs14.x', 'nodejs12.x']; + for (const runtime of validRuntimes) { + jest.clearAllMocks(); + core.getInput.mockImplementation((inputName) => { + if (inputName === 'function-name') return 'test-function'; + if (inputName === 'region') return 'us-east-1'; + if (inputName === 'code-artifacts-dir') return './artifacts'; + if (inputName === 'handler') return 'index.handler'; + if (inputName === 'runtime') return runtime; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + expect(result.runtime).toBe(runtime); + expect(core.setFailed).not.toHaveBeenCalled(); + } + }); + test('default to 20js.x', () => { + jest.clearAllMocks(); + core.getInput.mockImplementation((inputName) => { + if (inputName === 'function-name') return 'test-function'; + if (inputName === 'region') return 'us-east-1'; + if (inputName === 'code-artifacts-dir') return './artifacts'; + if (inputName === 'handler') return 'index.handler'; + if (inputName === 'runtime') return ''; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + expect(result.runtime).toBe('node20js.x'); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + }); + }); + describe('ARN Input Validation Tests', () => { + describe('Role ARN Validation', () => { + test('should validate role ARN format', () => { + core.getInput.mockImplementation((name) => { + if (name === 'role') return 'arn:aws:iam::123456789012:role/test-role'; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + }); + test('should reject invalid role ARN format', () => { + core.getInput.mockImplementation((name) => { + if (name === 'role') return 'invalid:arn:format'; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('Invalid IAM role ARN format') + ); + }); + }); + describe('Code Signing Config ARN Validation Test', () => { + test('should validate code signing config ARN format', () => { + core.getInput.mockImplementation((name) => { + if (name === 'code-signing-config-arn') return 'arn:aws:lambda:us-east-1:123456789012:code-signing-config:abc123'; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + }); + test('should reject invalid code signing config ARN format', () => { + core.getInput.mockImplementation((name) => { + if (name === 'code-signing-config-arn') return 'invalid:code:signing:arn'; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('Invalid code signing config ARN format') + ); + }); + }); + describe('KMS Key ARN Validation', () => { + test('should validate KMS key ARN format', () => { + core.getInput.mockImplementation((name) => { + if (name === 'kms-key-arn') return 'arn:aws:kms:us-east-1:123456789012:key/abcdef12-3456-7890-abcd-ef1234567890'; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + }); + test('should reject invalid source KMS key ARN format', () => { + core.getInput.mockImplementation((name) => { + if (name === 'kms-key-arn') return 'invalid:kms:key:arn'; + if (name === 'source-kms-key-arn') return 'invalid:kms:key:arn' + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('Invalid KMS key ARN format') + ); + }); + }); + }); + describe('JSON Input Validations', () => { + beforeEach(() => { + jest.resetAllMocks(); + core.getInput = jest.fn(); + core.getBooleanInput = jest.fn(); + core.setFailed = jest.fn(); + core.getInput.mockImplementation((name) => { + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './src', + 'handler': 'index.handler', + 'runtime': 'nodejs18.x' + }; + return inputs[name] || ''; + }); + }); + describe('Environment validation', () => { + test('should accept valid environment variables', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'environment') { + return '{"ENV":"prod","DEBUG":"true","API_URL":"https://api.example.com"}' + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './src', + 'handler': 'index.handler', + 'runtime': 'nodejs18.x' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + expect(result.parsedEnvironment).toEqual({ + ENV: 'prod', + DEBUG: 'true', + API_URL: 'https://api.example.com' + }); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + test('should reject invalid JSON in environment', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'environment') { + return '{"ENV":"prod", DEBUG:"true"}'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './src', + 'handler': 'index.handler', + 'runtime': 'nodejs18.x' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('Input validation error')); + expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('Invalid JSON in environment')); + }); + }); + describe('vpc-config validation', () => { + test('should accept valid vpc configuration', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'vpc-config') { + return '{"SubnetIds":["subnet-123","subnet-456"],"SecurityGroupIds":["sg-123"]}'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './src', + 'handler': 'index.handler', + 'runtime': 'nodejs18.x' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + expect(result.parsedVpcConfig).toEqual({ + SubnetIds: ['subnet-123', 'subnet-456'], + SecurityGroupIds: ['sg-123'] + }); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + test('should reject vpc-config missing SubnetIds', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'vpc-config') { + return '{"SecurityGroupIds":["sg-123"]}'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './src' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('vpc-config must include \'SubnetIds\'')); + }); + test('should reject vpc-config with non-array SubnetIds', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'vpc-config') { + return '{"SubnetIds": "subnet-123", "SecurityGroupIds":["sg-123"]}'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './src' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('vpc-config must include \'SubnetIds\' as an array')); + }); + test('should reject vpc-config missing SecurityGroupIds', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'vpc-config') { + return '{"SubnetIds":["subnet-123","subnet-456"]}'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './test-dir', + 'handler': 'index.handler', + 'runtime': 'nodejs18.x' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('vpc-config must include \'SecurityGroupIds\'')); + }); + test('should reject vpc-config with non-array SecurityGroupIds', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'vpc-config') { + return '{"SubnetIds":["subnet-123","subnet-456"],"SecurityGroupIds":"sg-123"}'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './test-dir' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('vpc-config must include \'SecurityGroupIds\' as an array')); + }); + }); + describe('dead-letter-config validation', () => { + test('should accept valid dead letter configuration', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'dead-letter-config') { + return '{"TargetArn":"arn:aws:sns:us-east-1:123456789012:my-topic"}'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './test-dir' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + expect(result.parsedDeadLetterConfig).toEqual({ + TargetArn: 'arn:aws:sns:us-east-1:123456789012:my-topic' + }); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + test('should reject dead-letter-config missing TargetArn', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'dead-letter-config') { + return '{"SomeOtherProperty":"value"}'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './test-dir' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('dead-letter-config must include \'TargetArn\'')); + }); + }); + describe('tracing-config validation', () => { + test('should accept valid tracing configuration with Active mode', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'tracing-config') { + return '{"Mode":"Active"}'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './test-dir' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + expect(result.parsedTracingConfig).toEqual({ + Mode: 'Active' + }); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + test('should accept valid tracing configuration with PassThrough mode', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'tracing-config') { + return '{"Mode":"PassThrough"}'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './test-dir' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + expect(result.parsedTracingConfig).toEqual({ + Mode: 'PassThrough' + }); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + test('should reject tracing-config with invalid Mode', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'tracing-config') { + return '{"Mode":"InvalidMode"}'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './test-dir' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('tracing-config Mode must be \'Active\' or \'PassThrough\'')); + }); + test('should reject tracing-config missing Mode', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'tracing-config') { + return '{"SomeOtherProperty":"value"}'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './test-dir' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('tracing-config Mode must be \'Active\' or \'PassThrough\'')); + }); + }); + describe('layers validation', () => { + test('should accept valid layers array', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'layers') { + return '["arn:aws:lambda:us-east-1:123456789012:layer:layer1:1","arn:aws:lambda:us-east-1:123456789012:layer:layer2:2"]'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './test-dir' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + expect(result.parsedLayers).toEqual([ + 'arn:aws:lambda:us-east-1:123456789012:layer:layer1:1', + 'arn:aws:lambda:us-east-1:123456789012:layer:layer2:2' + ]); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + test('should reject layers as non-array', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'layers') { + return '{"layer":"arn:aws:lambda:us-east-1:123456789012:layer:layer1:1"}'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './test-dir' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('layers must be an array of layer ARNs')); + }); + }); + describe('file-system-configs validation', () => { + test('should accept valid file system configs array', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'file-system-configs') { + return '[{"Arn":"arn:aws:elasticfilesystem:us-east-1:123456789012:access-point/fsap-123","LocalMountPath":"/mnt/efs"}]'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './test-dir' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + expect(result.parsedFileSystemConfigs).toEqual([ + { + Arn: 'arn:aws:elasticfilesystem:us-east-1:123456789012:access-point/fsap-123', + LocalMountPath: '/mnt/efs' + } + ]); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + test('should reject file-system-configs as non-array', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'file-system-configs') { + return '{"Arn":"arn:aws:elasticfilesystem:us-east-1:123456789012:access-point/fsap-123","LocalMountPath":"/mnt/efs"}'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './test-dir' }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('file-system-configs must be an array')); + }); + test('should reject file-system-configs missing Arn', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'file-system-configs') { + return '[{"LocalMountPath":"/mnt/efs"}]'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './test-dir' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('Each file-system-config must include \'Arn\' and \'LocalMountPath\'')); + }); + test('should reject file-system-configs missing LocalMountPath', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'file-system-configs') { + return '[{"Arn":"arn:aws:elasticfilesystem:us-east-1:123456789012:access-point/fsap-123"}]'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './test-dir' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('Each file-system-config must include \'Arn\' and \'LocalMountPath\'')); + }); + }); + describe('snap-start validation', () => { + test('should accept valid snap-start with PublishedVersions', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'snap-start') { + return '{"ApplyOn":"PublishedVersions"}'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './test-dir' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + expect(result.parsedSnapStart).toEqual({ + ApplyOn: 'PublishedVersions' + }); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + test('should accept valid snap-start with None', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'snap-start') { + return '{"ApplyOn":"None"}'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './test-dir' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + expect(result.parsedSnapStart).toEqual({ + ApplyOn: 'None' + }); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + test('should reject snap-start with invalid ApplyOn', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'snap-start') { + return '{"ApplyOn":"Invalid"}'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './test-dir' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('snap-start ApplyOn must be \'PublishedVersions\' or \'None\'')); + }); + test('should reject snap-start missing ApplyOn', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'snap-start') { + return '{"SomeOtherProperty":"value"}'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './test-dir' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('snap-start ApplyOn must be \'PublishedVersions\' or \'None\'')); + }); + }); + describe('tags validation', () => { + test('should accept valid tags object', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'tags') { + return '{"Environment":"Production","Team":"DevOps","Project":"Lambda-Action"}'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './test-dir' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + expect(result.parsedTags).toEqual({ + Environment: 'Production', + Team: 'DevOps', + Project: 'Lambda-Action' + }); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + test('should reject tags as array', () => { + const mockGetInput = jest.fn((name) => { + if (name === 'tags') { + return '["tag1", "tag2"]'; + } + const inputs = { + 'function-name': 'test-function', + 'region': 'us-east-1', + 'code-artifacts-dir': './test-dir' + }; + return inputs[name] || ''; + }); + core.getInput = mockGetInput; + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('tags must be an object of key-value pairs')); + }); + }); + describe('VPC Configuration Edge Cases', () => { + test('should reject vpc-config with malformed SubnetIds', () => { + const invalidVpcConfig = JSON.stringify({ + SubnetIds: "subnet-123", + SecurityGroupIds: ['sg-123'] + }); + core.getInput.mockImplementation((name) => { + if (name === 'vpc-config') return invalidVpcConfig; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + if (name === 'handler') return 'index.handler'; + if (name === 'runtime') return 'nodejs18.x'; + if (name === 'handler') return 'index.handler'; + if (name === 'runtime') return 'nodejs18.x'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining("vpc-config must include 'SubnetIds' as an array") + ); + }); + test('should reject vpc-config with empty SecurityGroupIds array', () => { + const validVpcConfig = JSON.stringify({ + SubnetIds: ['subnet-123'], + SecurityGroupIds: [] + }); + core.getInput.mockImplementation((name) => { + if (name === 'vpc-config') return validVpcConfig; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + if (name === 'handler') return 'index.handler'; + if (name === 'runtime') return 'nodejs18.x'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + }); + }); + describe('Dead Letter Config Validation', () => { + test('should validate SQS ARN in dead-letter-config', () => { + const validDLQConfig = JSON.stringify({ + TargetArn: 'arn:aws:sqs:us-east-1:123456789012:my-queue' + }); + core.getInput.mockImplementation((name) => { + if (name === 'dead-letter-config') return validDLQConfig; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + if (name === 'handler') return 'index.handler'; + if (name === 'runtime') return 'nodejs18.x'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + }); + test('should validate SNS ARN in dead-letter-config', () => { + const validDLQConfig = JSON.stringify({ + TargetArn: 'arn:aws:sns:us-east-1:123456789012:my-topic' + }); + core.getInput.mockImplementation((name) => { + if (name === 'dead-letter-config') return validDLQConfig; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + if (name === 'handler') return 'index.handler'; + if (name === 'runtime') return 'nodejs18.x'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + }); + }); + describe('Invalid JSON Handling', () => { + test('should handle invalid JSON format in vpc-config', () => { + const invalidJson = '{ this is not valid JSON }'; + core.getInput.mockImplementation((name) => { + if (name === 'vpc-config') return invalidJson; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('Invalid JSON in vpc-config') + ); + }); + test('should handle invalid JSON format in environment', () => { + const invalidJson = '{ ENV: production }'; + core.getInput.mockImplementation((name) => { + if (name === 'environment') return invalidJson; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + if (name === 'handler') return 'index.handler'; + if (name === 'runtime') return 'nodejs18.x'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('Invalid JSON in environment') + ); + }); + }); + describe('Tracing Config Validation', () => { + test('should reject invalid tracing mode values', () => { + const invalidTracingConfig = JSON.stringify({ + Mode: 'Detailed' + }); + core.getInput.mockImplementation((name) => { + if (name === 'tracing-config') return invalidTracingConfig; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining("tracing-config Mode must be 'Active' or 'PassThrough'") + ); + }); + }); + describe('SnapStart Config Validation', () => { + test('should validate PublishedVersions for snap-start', () => { + const validSnapStart = JSON.stringify({ + ApplyOn: 'PublishedVersions' + }); + core.getInput.mockImplementation((name) => { + if (name === 'snap-start') return validSnapStart; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + }); + test('should validate None for snap-start', () => { + const validSnapStart = JSON.stringify({ + ApplyOn: 'None' + }); + core.getInput.mockImplementation((name) => { + if (name === 'snap-start') return validSnapStart; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + }); + test('should reject invalid ApplyOn values', () => { + const invalidSnapStart = JSON.stringify({ + ApplyOn: 'AllVersions' + }); + core.getInput.mockImplementation((name) => { + if (name === 'snap-start') return invalidSnapStart; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining("snap-start ApplyOn must be 'PublishedVersions' or 'None'") + ); + }); + }); + describe('File System Configs Validation', () => { + test('should reject non-array file-system-configs', () => { + const invalidFSConfig = JSON.stringify({ + Arn: 'arn:aws:efs:us-east-1:123456789012:access-point/fsap-12345', + LocalMountPath: '/mnt/efs' + }); + core.getInput.mockImplementation((name) => { + if (name === 'file-system-configs') return invalidFSConfig; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining("file-system-configs must be an array") + ); + }); + test('should reject file-system-configs with missing Arn', () => { + const invalidFSConfig = JSON.stringify([ + { + LocalMountPath: '/mnt/efs' + } + ]); + core.getInput.mockImplementation((name) => { + if (name === 'file-system-configs') return invalidFSConfig; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining("Each file-system-config must include 'Arn' and 'LocalMountPath'") + ); + }); + test('should validate multiple file system configs', () => { + const validFSConfig = JSON.stringify([ + { + Arn: 'arn:aws:efs:us-east-1:123456789012:access-point/fsap-12345', + LocalMountPath: '/mnt/efs1' + }, + { + Arn: 'arn:aws:efs:us-east-1:123456789012:access-point/fsap-67890', + LocalMountPath: '/mnt/efs2' + } + ]); + core.getInput.mockImplementation((name) => { + if (name === 'file-system-configs') return validFSConfig; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + }); + }); + describe('Tags Validation', () => { + test('should validate complex tag objects', () => { + const validTags = JSON.stringify({ + Environment: 'Production', + Project: 'Lambda-Action', + Team: 'DevOps', + Cost: 'Center123', + 'Complex Key': 'Value with spaces' + }); + core.getInput.mockImplementation((name) => { + if (name === 'tags') return validTags; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(true); + }); + test('should reject tag arrays', () => { + const invalidTags = JSON.stringify([ + { key: 'Environment', value: 'Production' } + ]); + core.getInput.mockImplementation((name) => { + if (name === 'tags') return invalidTags; + if (name === 'function-name') return 'test-function'; + if (name === 'region') return 'us-east-1'; + if (name === 'code-artifacts-dir') return './artifacts'; + return ''; + }); + const result = originalValidations.validateAllInputs(); + expect(result.valid).toBe(false); + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining("tags must be an object of key-value pairs") + ); + }); + }); + }); + describe('validateAndResolvePath function', () => { + let originalPlatform; + beforeAll(() => { + originalPlatform = process.platform; + }); + afterAll(() => { + Object.defineProperty(process, 'platform', { + value: originalPlatform + }); + }); + beforeEach(() => { + process.cwd = jest.fn().mockReturnValue('/mock/cwd'); + }); + test('should resolve relative paths correctly', () => { + const basePath = '/base/path'; + const relativePath = './subdir/file.js'; + const result = validateAndResolvePath(relativePath, basePath); + expect(result).toBe('/base/path/subdir/file.js'); + }); + test('should allow absolute paths that are inside base path', () => { + const basePath = '/base/path'; + const absolutePath = '/base/path/subdir/file.js'; + const result = validateAndResolvePath(absolutePath, basePath); + expect(result).toBe('/base/path/subdir/file.js'); + }); + test('should handle paths with no traversal correctly', () => { + const basePath = '/base/path'; + const safePath = 'subdir/file.js'; + const result = validateAndResolvePath(safePath, basePath); + expect(result).toBe('/base/path/subdir/file.js'); + }); + test('should throw error for path traversal with ../', () => { + const basePath = '/base/path'; + const maliciousPath = '../outside/file.js'; + expect(() => { + validateAndResolvePath(maliciousPath, basePath); + }).toThrow(/Security error: Path traversal attempt detected/); + }); + test('should throw error for path traversal with multiple ../', () => { + const basePath = '/base/path'; + const maliciousPath = 'subdir/../../outside/file.js'; + expect(() => { + validateAndResolvePath(maliciousPath, basePath); + }).toThrow(/Security error: Path traversal attempt detected/); + }); + test('should throw error for absolute paths outside base path', () => { + const basePath = '/base/path'; + const maliciousPath = '/outside/path/file.js'; + expect(() => { + validateAndResolvePath(maliciousPath, basePath); + }).toThrow(/Security error: Path traversal attempt detected/); + }); + test('should normalize paths with redundant separators', () => { + const basePath = '/base/path'; + const messyPath = 'subdir///nested//file.js'; + const result = validateAndResolvePath(messyPath, basePath); + expect(result).toBe('/base/path/subdir/nested/file.js'); + }); + test('should handle absolute paths inside base path', () => { + const basePath = '/base/path'; + let result = validateAndResolvePath(basePath, basePath); + expect(result).toBe('/base/path'); + result = validateAndResolvePath(`${basePath}/subdir`, basePath); + expect(result).toBe('/base/path/subdir'); + }); + test('should handle empty paths', () => { + const basePath = '/base/path'; + const emptyPath = ''; + const result = validateAndResolvePath(emptyPath, basePath); + expect(result).toBe('/base/path'); + }); + test('should handle current directory path', () => { + const basePath = '/base/path'; + const currentDirPath = '.'; + const result = validateAndResolvePath(currentDirPath, basePath); + expect(result).toBe('/base/path'); + }); + test('should handle special characters in paths', () => { + const basePath = '/base/path'; + const specialCharsPath = 'subdir/file with spaces and $special#chars.js'; + const result = validateAndResolvePath(specialCharsPath, basePath); + expect(result).toBe('/base/path/subdir/file with spaces and $special#chars.js'); + }); + test('should handle edge case where path resolves to base directory', () => { + const basePath = '/base/path'; + const edgeCasePaths = [ + 'subdir/..', + 'subdir/nested/../..', + 'subdir/./nested/../../', + './subdir/../' + ]; + edgeCasePaths.forEach(edgeCasePath => { + const result = validateAndResolvePath(edgeCasePath, basePath); + expect(result).toBe('/base/path'); + }); + }); + test('should handle paths with dot directory references', () => { + const basePath = '/base/path'; + const pathWithDots = 'subdir/./nested/./file.js'; + const result = validateAndResolvePath(pathWithDots, basePath); + expect(result).toBe('/base/path/subdir/nested/file.js'); + }); + test('should handle complex absolute paths correctly', () => { + const basePath = '/base/path'; + const absolutePath = '/base/path/./subdir/../file.js'; + expect(() => { + validateAndResolvePath(absolutePath, basePath); + }).not.toThrow(); + const result = validateAndResolvePath(absolutePath, basePath); + expect(result).toBe('/base/path/file.js'); + }); + }); +}); + diff --git a/__tests__/wait_for_function_active.test.js b/__tests__/wait_for_function_active.test.js new file mode 100644 index 00000000..1f747245 --- /dev/null +++ b/__tests__/wait_for_function_active.test.js @@ -0,0 +1,180 @@ +const { waitForFunctionActive } = require('../index'); +const core = require('@actions/core'); + +jest.mock('fs/promises', () => { + return { + readFile: jest.fn().mockResolvedValue(Buffer.from('mock file content')), + + access: jest.fn().mockResolvedValue(undefined), + mkdir: jest.fn().mockResolvedValue(undefined), + stat: jest.fn().mockResolvedValue({ size: 1024 }), + + }; +}); +jest.mock('glob'); +jest.mock('adm-zip'); +jest.mock('@actions/core'); +jest.mock('path'); +jest.mock('os'); +jest.mock('../validations'); +jest.mock('@aws-sdk/client-lambda', () => { + const original = jest.requireActual('@aws-sdk/client-lambda'); + return { + ...original, + GetFunctionConfigurationCommand: jest.fn().mockImplementation((params) => ({ + ...params, + type: 'GetFunctionConfigurationCommand' + })), + CreateFunctionCommand: jest.fn().mockImplementation((params) => ({ + ...params, + type: 'CreateFunctionCommand' + })), + UpdateFunctionCodeCommand: jest.fn().mockImplementation((params) => ({ + input: params, + type: 'UpdateFunctionCodeCommand' + })), + UpdateFunctionConfigurationCommand: jest.fn().mockImplementation((params) => ({ + input: params, + type: 'UpdateFunctionConfigurationCommand' + })), + GetFunctionCommand: jest.fn().mockImplementation((params) => ({ + input: params, + type: 'GetFunctionCommand' + })), + LambdaClient: jest.fn().mockImplementation(() => ({ + send: jest.fn() + })), + waitUntilFunctionUpdated: jest.fn() + }; +}); +jest.mock('@aws-sdk/client-s3', () => { + const original = jest.requireActual('@aws-sdk/client-s3'); + return { + ...original, + HeadBucketCommand: jest.fn().mockImplementation((params) => ({ + ...params, + type: 'HeadBucketCommand' + })), + CreateBucketCommand: jest.fn().mockImplementation((params) => ({ + ...params, + type: 'CreateBucketCommand' + })), + PutObjectCommand: jest.fn().mockImplementation((params) => ({ + ...params, + type: 'PutObjectCommand' + })), + PutBucketEncryptionCommand: jest.fn().mockImplementation((params) => ({ + ...params, + type: 'PutBucketEncryptionCommand' + })), + PutPublicAccessBlockCommand: jest.fn().mockImplementation((params) => ({ + ...params, + type: 'PutPublicAccessBlockCommand' + })), + PutBucketVersioningCommand: jest.fn().mockImplementation((params) => ({ + ...params, + type: 'PutBucketVersioningCommand' + })), + S3Client: jest.fn().mockImplementation(() => ({ + send: jest.fn().mockResolvedValue({}) + })) + }; +}); + + +describe('Wait For Function Active Test', () => { + beforeEach(() => { + jest.useFakeTimers(); + core.info = jest.fn(); + core.warning = jest.fn(); + }); + afterEach(() => { + jest.useRealTimers(); + jest.clearAllMocks(); + }); + test('should throw error when function not found', async () => { + + const notFoundError = new Error('Function not found'); + notFoundError.name = 'ResourceNotFoundException'; + + const mockSend = jest.fn().mockRejectedValue(notFoundError); + const mockClient = { send: mockSend }; + + const functionPromise = waitForFunctionActive(mockClient, 'test-function'); + + jest.runOnlyPendingTimers(); + + await expect(functionPromise).rejects.toThrow('Function test-function not found'); + }); + test('should throw error on permission denied', async () => { + + const permissionError = new Error('Access denied'); + permissionError.$metadata = { httpStatusCode: 403 }; + + const mockSend = jest.fn().mockRejectedValue(permissionError); + const mockClient = { send: mockSend }; + + const functionPromise = waitForFunctionActive(mockClient, 'test-function'); + + jest.runOnlyPendingTimers(); + + await expect(functionPromise).rejects.toThrow( + 'Permission denied while checking function test-function status' + ); + }); + test('should log warning and retry on general errors', async () => { + + const generalError = new Error('Network issue'); + + const mockSend = jest.fn() + .mockRejectedValueOnce(generalError) + .mockResolvedValueOnce({ State: 'Active' }); + const mockClient = { send: mockSend }; + + const functionPromise = waitForFunctionActive(mockClient, 'test-function'); + + jest.runOnlyPendingTimers(); + await Promise.resolve(); + + jest.advanceTimersByTime(5000); + await Promise.resolve(); + + await functionPromise; + + expect(core.warning).toHaveBeenCalledWith('Function status check error: Network issue'); + expect(core.info).toHaveBeenCalledWith('Function test-function is now active'); + }); + test('should time out if function never becomes active', async () => { + + const mockSend = jest.fn().mockResolvedValue({ State: 'Pending' }); + const mockClient = { send: mockSend }; + + const waitMinutes = 1; + const functionPromise = waitForFunctionActive(mockClient, 'test-function', waitMinutes); + + + for (let i = 0; i < 13; i++) { + jest.advanceTimersByTime(5000); + await Promise.resolve(); + } + + await expect(functionPromise).rejects.toThrow( + `Timed out waiting for function test-function to become active after ${waitMinutes} minutes` + ); + }); + test('should cap wait time to maximum allowed', async () => { + + const mockSend = jest.fn().mockResolvedValue({ State: 'Active' }); + const mockClient = { send: mockSend }; + + const excessiveWaitMinutes = 60; + + const functionPromise = waitForFunctionActive(mockClient, 'test-function', excessiveWaitMinutes); + + jest.runOnlyPendingTimers(); + + await functionPromise; + + expect(core.info).toHaveBeenCalledWith('Wait time capped to maximum of 30 minutes'); + }); +}); diff --git a/__tests__/wait_for_function_updated.test.js b/__tests__/wait_for_function_updated.test.js new file mode 100644 index 00000000..114d5405 --- /dev/null +++ b/__tests__/wait_for_function_updated.test.js @@ -0,0 +1,117 @@ +const { waitForFunctionUpdated } = require('../index'); +const core = require('@actions/core'); +const { waitUntilFunctionUpdated } = require('@aws-sdk/client-lambda'); + +jest.mock('@actions/core'); +jest.mock('@aws-sdk/client-lambda', () => { + return { + GetFunctionConfigurationCommand: jest.fn(), + waitUntilFunctionUpdated: jest.fn() + }; +}); + +describe('waitForFunctionUpdated function', () => { + let mockLambdaClient; + + jest.setTimeout(60000); + beforeEach(() => { + jest.resetAllMocks(); + + + jest.useRealTimers(); + + core.info = jest.fn(); + core.warning = jest.fn(); + + mockLambdaClient = {}; + + waitUntilFunctionUpdated.mockReset(); + }); + test('should resolve when function update completes successfully', async () => { + + waitUntilFunctionUpdated.mockResolvedValue({}); + + await expect(waitForFunctionUpdated(mockLambdaClient, 'test-function')).resolves.toBeUndefined(); + + expect(waitUntilFunctionUpdated).toHaveBeenCalledWith( + expect.objectContaining({ + client: mockLambdaClient, + minDelay: 2, + maxWaitTime: 5 * 60 + }), + expect.objectContaining({ + FunctionName: 'test-function' + }) + ); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Function update completed successfully')); + }); + test('should use custom wait time when specified', async () => { + + waitUntilFunctionUpdated.mockResolvedValue({}); + const customWaitMinutes = 10; + + await expect(waitForFunctionUpdated(mockLambdaClient, 'test-function', customWaitMinutes)).resolves.toBeUndefined(); + + expect(waitUntilFunctionUpdated).toHaveBeenCalledWith( + expect.objectContaining({ + client: mockLambdaClient, + minDelay: 2, + maxWaitTime: customWaitMinutes * 60 + }), + expect.objectContaining({ + FunctionName: 'test-function' + }) + ); + }); + test('should handle waiter TimeoutError', async () => { + + const timeoutError = new Error('Waiter timed out'); + timeoutError.name = 'TimeoutError'; + waitUntilFunctionUpdated.mockRejectedValue(timeoutError); + + await expect(waitForFunctionUpdated(mockLambdaClient, 'test-function')) + .rejects.toThrow('Timed out waiting for function test-function update to complete after 5 minutes'); + }); + test('should handle ResourceNotFoundException error', async () => { + + const notFoundError = new Error('Function not found'); + notFoundError.name = 'ResourceNotFoundException'; + waitUntilFunctionUpdated.mockRejectedValue(notFoundError); + + await expect(waitForFunctionUpdated(mockLambdaClient, 'test-function')) + .rejects.toThrow('Function test-function not found'); + }); + test('should handle permission denied errors', async () => { + + const permissionError = new Error('Permission denied'); + permissionError.$metadata = { httpStatusCode: 403 }; + waitUntilFunctionUpdated.mockRejectedValue(permissionError); + + await expect(waitForFunctionUpdated(mockLambdaClient, 'test-function')) + .rejects.toThrow('Permission denied while checking function test-function status'); + }); + test('should handle other errors with appropriate message', async () => { + + const generalError = new Error('Something went wrong'); + waitUntilFunctionUpdated.mockRejectedValue(generalError); + + await expect(waitForFunctionUpdated(mockLambdaClient, 'test-function')) + .rejects.toThrow('Error waiting for function test-function update: Something went wrong'); + expect(core.warning).toHaveBeenCalledWith('Function update check error: Something went wrong'); + }); + test('should cap wait time to maximum allowed', async () => { + + waitUntilFunctionUpdated.mockResolvedValue({}); + + const excessiveWaitMinutes = 100; + await waitForFunctionUpdated(mockLambdaClient, 'test-function', excessiveWaitMinutes); + + expect(core.info).toHaveBeenCalledWith('Wait time capped to maximum of 30 minutes'); + expect(waitUntilFunctionUpdated).toHaveBeenCalledWith( + expect.objectContaining({ + maxWaitTime: 30 * 60 + }), + expect.any(Object) + ); + }); +}); diff --git a/action.yml b/action.yml new file mode 100644 index 00000000..42efbe0a --- /dev/null +++ b/action.yml @@ -0,0 +1,99 @@ +name: GitHub Action for Lambda Function Deployment +description: This action updates the code and configuration of the Lambda function provided by the user. If the function does not exist, a new one will be created. +inputs: + function-name: + description: 'Name of the Lambda function.' + required: true + code-artifacts-dir: + description: 'The path to a directory of code artifacts to zip and deploy to Lambda.' + required: true + handler: + description: 'The name of the method within your code that Lambda calls to run your function. Required for .zip file' + required: true + default: 'index.handler' + runtime: + description: 'The identifier of the runtime. Required for .zip file' + required: true + default: 'nodejs20.x' + s3-bucket: + description: 'S3 bucket name to use for Lambda deployment package. If provided, S3 deployment method will be used instead of direct upload.' + required: false + s3-key: + description: 'S3 key for the Lambda deployment package in the bucket. If not provided, a key will be auto-generated using the format: lambda-deployments/{function-name}/{timestamp}-{commit-hash}.zip.' + required: false + publish: + description: 'Set to true to publish a new version of the function after updating the code.' + required: false + dry-run: + description: 'Set true to validate the request parameters and access permissions without modifying the function code. Applicable for updating function code only. Creating and updating function configuration will be skipped since they do not support dry run.' + required: false + default: 'false' + revision-id: + description: 'Update the function only if the revision ID matches the ID that is specified.' + required: false + architectures: + description: 'The instruction set architecture that the function supports.' + required: false + source-kms-key-arn: + description: 'The ARN of the Key Management Service (KMS) customer managed key that is used to encrypt your functions .zip deployment package.' + required: false + role: + description: 'The Amazon Resource Name (ARN) of the functions execution role. Required when creating a new function.' + required: false + function-description: + description: 'A description of the function.' + required: false + memory-size: + description: 'The amount of memory available to the function at runtime.' + required: false + timeout: + description: 'The amount of time (in seconds) that Lambda allows a function to run before stopping it.' + required: false + vpc-config: + description: 'For network connectivity to Amazon Web Services resources in a VPC, specify a list of security groups and subnets in the VPC.' + required: false + environment: + description: 'Environment variables as a JSON string' + required: false + dead-letter-config: + description: 'Specifies the queue or topic where Lambda sends asynchronous events when they fail processing.' + required: false + kms-key-arn: + description: 'The ARN of the Key Management Service (KMS) customer managed key' + required: false + tracing-config: + description: 'Set Mode to Active to sample and trace a subset of incoming requests with X-Ray.' + required: false + layers: + description: 'A list of function layers to add to the functions execution environment.' + required: false + file-system-configs: + description: 'Connection settings for an Amazon EFS file system.' + required: false + image-config: + description: 'Configuration for the Lambda functions container image.' + required: false + ephemeral-storage: + description: 'The size of the functions /tmp directory in MB. The default value is 512, but can be any whole number between 512 and 10,240 MB.' + required: false + snap-start: + description: 'The functions SnapStart setting.' + required: false + logging-config: + description: 'The Amazon CloudWatch Logs configuration settings for the function.' + required: false + code-signing-config-arn: + description: 'The ARN of a code-signing configuration to use on this function.' + required: false + tags: + description: 'Tags to apply to the function as a JSON string (e.g. {"Environment":"Production","Team":"DevOps"})' + required: false +outputs: + function-arn: + description: 'The ARN of the updated Lambda function.' + version: + description: 'The function version if a new version was published.' + +runs: + using: 'node20' + main: 'dist/index.js' diff --git a/index.js b/index.js new file mode 100644 index 00000000..e07cf12c --- /dev/null +++ b/index.js @@ -0,0 +1,1134 @@ +const core = require('@actions/core'); +const { LambdaClient, CreateFunctionCommand, GetFunctionConfigurationCommand, UpdateFunctionConfigurationCommand, UpdateFunctionCodeCommand, waitUntilFunctionUpdated } = require('@aws-sdk/client-lambda'); +const { S3Client, PutObjectCommand, CreateBucketCommand, HeadBucketCommand, PutBucketEncryptionCommand, PutPublicAccessBlockCommand, PutBucketVersioningCommand} = require('@aws-sdk/client-s3'); +const { STSClient, GetCallerIdentityCommand } = require('@aws-sdk/client-sts'); +const fs = require('fs/promises'); +const path = require('path'); +const AdmZip = require('adm-zip'); +const validations = require('./validations'); +const { version } = require('./package.json'); +async function run() { + try { + + // Receiving and validating inputs + const inputs = validations.validateAllInputs(); + if (!inputs.valid) { + return; + } + + const { + functionName, codeArtifactsDir, + ephemeralStorage, parsedMemorySize, timeout, + role, codeSigningConfigArn, kmsKeyArn, sourceKmsKeyArn, + environment, vpcConfig, deadLetterConfig, tracingConfig, + layers, fileSystemConfigs, imageConfig, snapStart, + loggingConfig, tags, + parsedEnvironment, parsedVpcConfig, parsedDeadLetterConfig, + parsedTracingConfig, parsedLayers, parsedFileSystemConfigs, + parsedImageConfig, parsedSnapStart, parsedLoggingConfig, parsedTags, + functionDescription, dryRun, publish, revisionId, + runtime, handler, architectures + } = inputs; + + const region = process.env.AWS_REGION; + + // Set up custom user agent string + const customUserAgentString = `LambdaGitHubAction/${version}`; + core.info(`Setting custom user agent: ${customUserAgentString}`); + + // Creating new Lambda client + const client = new LambdaClient({ + region, + customUserAgent: customUserAgentString + }); + + // Handling S3 Buckets + const { s3Bucket, useS3Method } = inputs; + let s3Key = inputs.s3Key; + if (s3Bucket && !s3Key) { + s3Key = generateS3Key(functionName); + core.info(`No S3 key provided. Auto-generated key: ${s3Key}`); + } + + // Determine if function exists + let functionExists; + if (!dryRun) { + core.info(`Checking if ${functionName} exists`); + functionExists = await checkFunctionExists(client, functionName); + } + if (dryRun) { + core.info('DRY RUN MODE: No AWS resources will be created or modified'); + if (!functionExists) { + core.setFailed('DRY RUN MODE can only be used for updating function code of existing functions'); + return; + } + } + + // Creating zip file + core.info(`Packaging code artifacts from ${codeArtifactsDir}`); + let finalZipPath = await packageCodeArtifacts(codeArtifactsDir); + + // Create function + await createFunction(client, { + functionName, region, finalZipPath, dryRun, role, + s3Bucket, s3Key, sourceKmsKeyArn, runtime, handler, + functionDescription, parsedMemorySize, timeout, + publish, architectures, ephemeralStorage, + revisionId, vpcConfig, parsedEnvironment, deadLetterConfig, + tracingConfig, layers, fileSystemConfigs, imageConfig, + snapStart, loggingConfig, tags, kmsKeyArn, codeSigningConfigArn, + parsedVpcConfig, parsedDeadLetterConfig, parsedTracingConfig, + parsedLayers, parsedFileSystemConfigs, parsedImageConfig, + parsedSnapStart, parsedLoggingConfig, parsedTags + }, functionExists); + + // Update function configuration + core.info(`Getting current configuration for function ${functionName}`); + const configCommand = new GetFunctionConfigurationCommand({FunctionName: functionName}); + let currentConfig = await client.send(configCommand); + + const configChanged = hasConfigurationChanged(currentConfig, { + ...(role && { Role: role }), + ...(handler && { Handler: handler }), + ...(functionDescription && { Description: functionDescription }), + ...(parsedMemorySize && { MemorySize: parsedMemorySize }), + ...(timeout && { Timeout: timeout }), + ...(runtime && { Runtime: runtime }), + ...(kmsKeyArn && { KMSKeyArn: kmsKeyArn }), + ...(ephemeralStorage && { EphemeralStorage: { Size: ephemeralStorage } }), + ...(vpcConfig && { VpcConfig: parsedVpcConfig }), + Environment: { Variables: parsedEnvironment }, + ...(deadLetterConfig && { DeadLetterConfig: parsedDeadLetterConfig }), + ...(tracingConfig && { TracingConfig: parsedTracingConfig }), + ...(layers && { Layers: parsedLayers }), + ...(fileSystemConfigs && { FileSystemConfigs: parsedFileSystemConfigs }), + ...(imageConfig && { ImageConfig: parsedImageConfig }), + ...(snapStart && { SnapStart: parsedSnapStart }), + ...(loggingConfig && { LoggingConfig: parsedLoggingConfig }) + }); + + if (configChanged) { + if (dryRun) { + core.info('[DRY RUN] Configuration updates are not simulated in dry run mode'); + return; + } + + await updateFunctionConfiguration(client, { + functionName, + role, + handler, + functionDescription, + parsedMemorySize, + timeout, + runtime, + kmsKeyArn, + ephemeralStorage, + vpcConfig, + parsedEnvironment, + deadLetterConfig, + tracingConfig, + layers, + fileSystemConfigs, + imageConfig, + snapStart, + loggingConfig, + parsedVpcConfig, + parsedDeadLetterConfig, + parsedTracingConfig, + parsedLayers, + parsedFileSystemConfigs, + parsedImageConfig, + parsedSnapStart, + parsedLoggingConfig + }); + } else { + core.info('No configuration changes detected'); + } + + // Update Function Code + await updateFunctionCode(client, { + functionName, + finalZipPath, + useS3Method, + s3Bucket, + s3Key, + codeArtifactsDir, + architectures, + publish, + revisionId, + sourceKmsKeyArn, + dryRun, + region + }); + + core.info('Lambda function deployment completed successfully'); + + } catch (error) { + if (error.name === 'ThrottlingException' || error.name === 'TooManyRequestsException' || error.$metadata?.httpStatusCode === 429) { + core.setFailed(`Rate limit exceeded and maximum retries reached: ${error.message}`); + } else if (error.$metadata?.httpStatusCode >= 500) { + core.setFailed(`Server error (${error.$metadata?.httpStatusCode}): ${error.message}. All retry attempts failed.`); + } else if (error.name === 'AccessDeniedException') { + core.setFailed(`Action failed with error: Permissions error: ${error.message}. Check IAM roles.`); + } else { + core.setFailed(`Action failed with error: ${error.message}`); + } + if (error.stack) { + core.debug(error.stack); + } + } +} + +// Helper functions for zip files +async function packageCodeArtifacts(artifactsDir) { + const tempDir = path.join(require('os').tmpdir(), `lambda-temp-${Date.now()}`); + const zipPath = path.join(require('os').tmpdir(), `lambda-function-${Date.now()}.zip`); + + try { + try { + await fs.rm(tempDir, { recursive: true, force: true }); + } catch (error) { + } + + await fs.mkdir(tempDir, { recursive: true }); + + const workingDir = process.cwd(); + + if (!artifactsDir) { + throw new Error('Code artifacts directory path must be provided'); + } + + const resolvedArtifactsDir = validations.validateAndResolvePath(artifactsDir, workingDir); + + core.info(`Copying artifacts from ${resolvedArtifactsDir} to ${tempDir}`); + + try { + await fs.access(resolvedArtifactsDir); + } catch (error) { + throw new Error(`Code artifacts directory '${resolvedArtifactsDir}' does not exist or is not accessible: ${error.message}`); + } + + const sourceFiles = await fs.readdir(resolvedArtifactsDir); + + if (sourceFiles.length === 0) { + throw new Error(`Code artifacts directory '${resolvedArtifactsDir}' is empty, no files to package`); + } + + core.info(`Found ${sourceFiles.length} files/directories to copy`); + + for (const file of sourceFiles) { + const sourcePath = path.join(resolvedArtifactsDir, file); + const destPath = path.join(tempDir, file); + + core.info(`Copying ${sourcePath} to ${destPath}`); + + await fs.cp( + sourcePath, + destPath, + { recursive: true } + ); + } + + core.info('Creating ZIP file with standard options'); + const zip = new AdmZip(); + + const tempFiles = await fs.readdir(tempDir, { withFileTypes: true }); + + for (const file of tempFiles) { + const fullPath = path.join(tempDir, file.name); + + if (file.isDirectory()) { + core.info(`Adding directory: ${file.name}`); + zip.addLocalFolder(fullPath, file.name); + } else { + core.info(`Adding file: ${file.name}`); + zip.addLocalFile(fullPath); + } + } + + core.info('Writing ZIP file with standard options'); + zip.writeZip(zipPath); + + try { + const stats = await fs.stat(zipPath); + core.info(`Generated ZIP file size: ${stats.size} bytes`); + + const verifyZip = new AdmZip(zipPath); + const entries = verifyZip.getEntries(); + + core.info(`ZIP verification passed - contains ${entries.length} entries:`); + for (let i = 0; i < entries.length; i++) { + core.info(` ${i+1}. ${entries[i].entryName} (${entries[i].header.size} bytes)`); + } + } catch (error) { + throw new Error(`ZIP validation failed: ${error.message}`); + } + + return zipPath; + } catch (error) { + core.error(`Failed to package artifacts: ${error.message}`); + throw error; + } +} + + +//Helper function for checking if function exists +async function checkFunctionExists(client, functionName) { + try { + const input = { + FunctionName: functionName + }; + const command = new GetFunctionConfigurationCommand(input); + await client.send(command); + return true; + } catch (error) { + if (error.name === 'ResourceNotFoundException') { + return false; + } + throw error; + } +} + +// Helper functions for creating Lambda function +async function createFunction(client, inputs, functionExists) { + const { + functionName, region, finalZipPath, dryRun, role, s3Bucket, s3Key, + sourceKmsKeyArn, runtime, handler, functionDescription, parsedMemorySize, + timeout, publish, architectures, ephemeralStorage, revisionId, + vpcConfig, parsedEnvironment, deadLetterConfig, tracingConfig, + layers, fileSystemConfigs, imageConfig, snapStart, loggingConfig, tags, + kmsKeyArn, codeSigningConfigArn, parsedVpcConfig, parsedDeadLetterConfig, + parsedTracingConfig, parsedLayers, parsedFileSystemConfigs, parsedImageConfig, + parsedSnapStart, parsedLoggingConfig, parsedTags + } = inputs; + + if (!functionExists) { + if (dryRun) { + core.setFailed('DRY RUN MODE can only be used for updating function code of existing functions'); + return; + } + + core.info(`Function ${functionName} doesn't exist, creating new function`); + + if(!role) { + core.setFailed('Role ARN must be provided when creating a new function'); + return; + } + + try { + core.info('Creating Lambda function with deployment package'); + + let codeParameter; + + if (s3Bucket) { + try { + await uploadToS3(finalZipPath, s3Bucket, s3Key, region); + core.info(`Successfully uploaded package to S3: s3://${s3Bucket}/${s3Key}`); + + codeParameter = { + S3Bucket: s3Bucket, + S3Key: s3Key, + ...(sourceKmsKeyArn && { SourceKmsKeyArn: sourceKmsKeyArn }) + }; + } catch (error) { + core.setFailed(`Failed to upload package to S3: ${error.message}`); + if (error.stack) { + core.debug(error.stack); + } + throw error; + } + } else { + try { + const zipFileContent = await fs.readFile(finalZipPath); + core.info(`Zip file read successfully, size: ${zipFileContent.length} bytes`); + + codeParameter = { + ZipFile: zipFileContent, + ...(sourceKmsKeyArn && { SourceKmsKeyArn: sourceKmsKeyArn }) + }; + } catch (error) { + if (error.code === 'EACCES') { + core.setFailed(`Failed to read Lambda deployment package: Permission denied`); + core.error('Permission denied. Check file access permissions.'); + } else { + core.setFailed(`Failed to read Lambda deployment package: ${error.message}`); + } + if (error.stack) { + core.debug(error.stack); + } + throw error; + } + } + + const input = { + FunctionName: functionName, + Code: codeParameter, + ...(runtime && { Runtime: runtime }), + ...(role && { Role: role }), + ...(handler && { Handler: handler }), + ...(functionDescription && { Description: functionDescription }), + ...(parsedMemorySize && { MemorySize: parsedMemorySize }), + ...(timeout && { Timeout: timeout }), + ...(publish !== undefined && { Publish: publish }), + ...(architectures && { Architectures: Array.isArray(architectures) ? architectures : [architectures] }), + ...(ephemeralStorage && { EphemeralStorage: { Size: ephemeralStorage } }), + ...(revisionId && { RevisionId: revisionId }), + ...(vpcConfig && { VpcConfig: parsedVpcConfig }), + Environment: { Variables: parsedEnvironment }, + ...(deadLetterConfig && { DeadLetterConfig: parsedDeadLetterConfig }), + ...(tracingConfig && { TracingConfig: parsedTracingConfig }), + ...(layers && { Layers: parsedLayers }), + ...(fileSystemConfigs && { FileSystemConfigs: parsedFileSystemConfigs }), + ...(imageConfig && { ImageConfig: parsedImageConfig }), + ...(snapStart && { SnapStart: parsedSnapStart }), + ...(loggingConfig && { LoggingConfig: parsedLoggingConfig }), + ...(tags && { Tags: parsedTags }), + ...(kmsKeyArn && { KMSKeyArn: kmsKeyArn }), + ...(codeSigningConfigArn && { CodeSigningConfigArn: codeSigningConfigArn }), + }; + + core.info(`Creating new Lambda function: ${functionName}`); + const command = new CreateFunctionCommand(input); + const response = await client.send(command); + + core.setOutput('function-arn', response.FunctionArn); + if (response.Version) { + core.setOutput('version', response.Version); + } + + core.info('Lambda function created successfully'); + + core.info(`Waiting for function ${functionName} to become active before proceeding`); + await waitForFunctionActive(client, functionName); + } catch (error) { + if (error.name === 'ThrottlingException' || error.name === 'TooManyRequestsException' || error.$metadata?.httpStatusCode === 429) { + core.setFailed(`Rate limit exceeded and maximum retries reached: ${error.message}`); + } else if (error.$metadata?.httpStatusCode >= 500) { + core.setFailed(`Server error (${error.$metadata?.httpStatusCode}): ${error.message}. All retry attempts failed.`); + } else if (error.name === 'AccessDeniedException') { + core.setFailed(`Action failed with error: Permissions error: ${error.message}. Check IAM roles.`); + } else { + core.setFailed(`Failed to create function: ${error.message}`); + } + + if (error.stack) { + core.debug(error.stack); + } + throw error; + } + } +} + +async function waitForFunctionActive(client, functionName, waitForMinutes = 5) { + const MAX_WAIT_MINUTES = 30; + + if (waitForMinutes > MAX_WAIT_MINUTES) { + waitForMinutes = MAX_WAIT_MINUTES; + core.info(`Wait time capped to maximum of ${MAX_WAIT_MINUTES} minutes`); + } + + core.info(`Waiting for function ${functionName} to become active. Will wait for up to ${waitForMinutes} minutes`); + + const startTime = Date.now(); + const maxWaitTimeMs = waitForMinutes * 60 * 1000; + const DELAY_BETWEEN_CHECKS_MS = 5000; + let lastState = null; + + while (Date.now() - startTime < maxWaitTimeMs) { + try { + const command = new GetFunctionConfigurationCommand({ FunctionName: functionName }); + const response = await client.send(command); + + const currentState = response.State; + + if (currentState !== lastState) { + core.info(`Function ${functionName} is in state: ${currentState}`); + lastState = currentState; + } + + if (currentState === 'Active') { + core.info(`Function ${functionName} is now active`); + return; + } else if (currentState === 'Failed') { + throw new Error(`Function ${functionName} deployment failed with reason: ${response.StateReason || 'Unknown reason'}`); + } + + await new Promise(resolve => setTimeout(resolve, DELAY_BETWEEN_CHECKS_MS)); + } catch (error) { + if (error.name === 'ResourceNotFoundException') { + throw new Error(`Function ${functionName} not found`); + } else if (error.$metadata && error.$metadata.httpStatusCode === 403) { + throw new Error(`Permission denied while checking function ${functionName} status`); + } else { + core.warning(`Function status check error: ${error.message}`); + await new Promise(resolve => setTimeout(resolve, DELAY_BETWEEN_CHECKS_MS)); + } + } + } + + throw new Error(`Timed out waiting for function ${functionName} to become active after ${waitForMinutes} minutes`); +} + +// Helper functions for updating Lambda function configuration +async function updateFunctionConfiguration(client, params) { + const { + functionName, role, handler, functionDescription, parsedMemorySize, + timeout, runtime, kmsKeyArn, ephemeralStorage, vpcConfig, + parsedEnvironment, deadLetterConfig, tracingConfig, layers, + fileSystemConfigs, imageConfig, snapStart, loggingConfig, + parsedVpcConfig, parsedDeadLetterConfig, parsedTracingConfig, + parsedLayers, parsedFileSystemConfigs, parsedImageConfig, + parsedSnapStart, parsedLoggingConfig + } = params; + + try { + const input = { + FunctionName: functionName, + ...(role && { Role: role }), + ...(handler && { Handler: handler }), + ...(functionDescription && { Description: functionDescription }), + ...(parsedMemorySize && { MemorySize: parsedMemorySize }), + ...(timeout && { Timeout: timeout }), + ...(runtime && { Runtime: runtime }), + ...(kmsKeyArn && { KMSKeyArn: kmsKeyArn }), + ...(ephemeralStorage && { EphemeralStorage: { Size: ephemeralStorage } }), + ...(vpcConfig && { VpcConfig: parsedVpcConfig }), + Environment: { Variables: parsedEnvironment }, + ...(deadLetterConfig && { DeadLetterConfig: parsedDeadLetterConfig }), + ...(tracingConfig && { TracingConfig: parsedTracingConfig }), + ...(layers && { Layers: parsedLayers }), + ...(fileSystemConfigs && { FileSystemConfigs: parsedFileSystemConfigs }), + ...(imageConfig && { ImageConfig: parsedImageConfig }), + ...(snapStart && { SnapStart: parsedSnapStart }), + ...(loggingConfig && { LoggingConfig: parsedLoggingConfig }) + }; + + core.info(`Updating function configuration for ${functionName}`); + const command = new UpdateFunctionConfigurationCommand(input); + await client.send(command); + await waitForFunctionUpdated(client, functionName); + } catch (error) { + if (error.name === 'ThrottlingException' || error.name === 'TooManyRequestsException' || error.$metadata?.httpStatusCode === 429) { + core.setFailed(`Rate limit exceeded and maximum retries reached: ${error.message}`); + } else if (error.$metadata?.httpStatusCode >= 500) { + core.setFailed(`Server error (${error.$metadata?.httpStatusCode}): ${error.message}. All retry attempts failed.`); + } else if (error.name === 'AccessDeniedException') { + core.setFailed(`Action failed with error: Permissions error: ${error.message}. Check IAM roles.`); + } else { + core.setFailed(`Failed to update function configuration: ${error.message}`); + } + + if (error.stack) { + core.debug(error.stack); + } + throw error; + } +} + +async function waitForFunctionUpdated(client, functionName, waitForMinutes = 5) { + const MAX_WAIT_MINUTES = 30; + + if (waitForMinutes > MAX_WAIT_MINUTES) { + waitForMinutes = MAX_WAIT_MINUTES; + core.info(`Wait time capped to maximum of ${MAX_WAIT_MINUTES} minutes`); + } + + core.info(`Waiting for function update to complete. Will wait for ${waitForMinutes} minutes`); + + try { + await waitUntilFunctionUpdated({ + client: client, + minDelay: 2, + maxWaitTime: waitForMinutes * 60, + }, { + FunctionName: functionName + }); + + core.info('Function update completed successfully'); + } catch (error) { + if (error.name === 'TimeoutError') { + throw new Error(`Timed out waiting for function ${functionName} update to complete after ${waitForMinutes} minutes`); + } else if (error.name === 'ResourceNotFoundException') { + throw new Error(`Function ${functionName} not found`); + } else if (error.$metadata && error.$metadata.httpStatusCode === 403) { + throw new Error(`Permission denied while checking function ${functionName} status`); + } else if (error.message && error.message.includes("currently in the following state: 'Pending'")) { + core.warning(`Function ${functionName} is in 'Pending' state. Waiting for it to become active...`); + await waitForFunctionActive(client, functionName, waitForMinutes); + core.info(`Function ${functionName} is now active`); + } else { + core.warning(`Function update check error: ${error.message}`); + throw new Error(`Error waiting for function ${functionName} update: ${error.message}`); + } + } +} + +// Helper function for updating Lambda function code +async function updateFunctionCode(client, params) { + const { + functionName, finalZipPath, useS3Method, s3Bucket, s3Key, + codeArtifactsDir, architectures, publish, revisionId, + sourceKmsKeyArn, dryRun, region + } = params; + + core.info(`Updating function code for ${functionName} with ${finalZipPath}`); + + try { + const commonCodeParams = { + FunctionName: functionName, + ...(architectures && { Architectures: Array.isArray(architectures) ? architectures : [architectures] }), + ...(publish !== undefined && { Publish: publish }), + ...(revisionId && { RevisionId: revisionId }), + ...(sourceKmsKeyArn && { SourceKmsKeyArn: sourceKmsKeyArn }) + }; + + let codeInput; + + if (useS3Method) { + core.info(`Using S3 deployment method with bucket: ${s3Bucket}, key: ${s3Key}`); + + await uploadToS3(finalZipPath, s3Bucket, s3Key, region); + core.info(`Successfully uploaded package to S3: s3://${s3Bucket}/${s3Key}`); + + codeInput = { + ...commonCodeParams, + S3Bucket: s3Bucket, + S3Key: s3Key + }; + } else { + let zipFileContent; + + try { + zipFileContent = await fs.readFile(finalZipPath); + } catch (error) { + core.setFailed(`Failed to read Lambda deployment package at ${finalZipPath}: ${error.message}`); + + if (error.code === 'ENOENT') { + core.error(`File not found. Ensure the code artifacts directory "${codeArtifactsDir}" contains the required files.`); + } else if (error.code === 'EACCES') { + core.error('Permission denied. Check file access permissions.'); + } + + if (error.stack) { + core.debug(error.stack); + } + + throw error; + } + + codeInput = { + ...commonCodeParams, + ZipFile: zipFileContent + }; + + core.info(`Original buffer length: ${zipFileContent.length} bytes`); + } + + if (dryRun) { + core.info(`[DRY RUN] Performing dry-run function code update with parameters:`); + const logInput = {...codeInput}; + if (logInput.ZipFile) { + logInput.ZipFile = ``; + } + core.info(JSON.stringify(logInput, null, 2)); + codeInput.DryRun = true; + + const command = new UpdateFunctionCodeCommand(codeInput); + const response = await client.send(command); + + core.info('[DRY RUN] Function code validation passed'); + core.setOutput('function-arn', response.FunctionArn || `arn:aws:lambda:${region}:000000000000:function:${functionName}`); + core.setOutput('version', response.Version || '$LATEST'); + core.info('[DRY RUN] Function code update simulation completed'); + } else { + const command = new UpdateFunctionCodeCommand(codeInput); + const response = await client.send(command); + core.setOutput('function-arn', response.FunctionArn); + if (response.Version) { + core.setOutput('version', response.Version); + } + } + } catch (error) { + if (error.name === 'ThrottlingException' || error.name === 'TooManyRequestsException' || error.$metadata?.httpStatusCode === 429) { + core.setFailed(`Rate limit exceeded and maximum retries reached: ${error.message}`); + } else if (error.$metadata?.httpStatusCode >= 500) { + core.setFailed(`Server error (${error.$metadata?.httpStatusCode}): ${error.message}. All retry attempts failed.`); + } else if (error.name === 'AccessDeniedException') { + core.setFailed(`Action failed with error: Permissions error: ${error.message}. Check IAM roles.`); + } else { + core.setFailed(`Failed to update function code: ${error.message}`); + } + + if (error.stack) { + core.debug(error.stack); + } + throw error; + } +} + +// Helper functions for checking if configuration has changed +async function hasConfigurationChanged(currentConfig, updatedConfig) { + if (!currentConfig || Object.keys(currentConfig).length === 0) { + return true; + } + + const cleanedUpdated = cleanNullKeys(updatedConfig) || {}; + let hasChanged = false; + + for (const [key, value] of Object.entries(cleanedUpdated)) { + if (value !== undefined) { + if (!(key in currentConfig)) { + core.info(`Configuration difference detected in ${key}`); + hasChanged = true; + continue; + } + + if (typeof value === 'object' && value !== null) { + if (!deepEqual(currentConfig[key] || {}, value)) { + core.info(`Configuration difference detected in ${key}`); + hasChanged = true; + } + } else if (currentConfig[key] !== value) { + core.info(`Configuration difference detected in ${key}: ${currentConfig[key]} -> ${value}`); + hasChanged = true; + } + } + } + + return hasChanged; +} + +function isEmptyValue(value) { + if (value === null || value === undefined || value === '') { + return true; + } + + if (Array.isArray(value)) { + return value.length === 0 || value.every(item => isEmptyValue(item)); + } + + if (typeof value === 'object') { + if ('SubnetIds' in value || 'SecurityGroupIds' in value) { + return false; + } + return Object.keys(value).length === 0 || + Object.values(value).every(val => isEmptyValue(val)); + } + + return false; +} + +function cleanNullKeys(obj) { + if (obj === null || obj === undefined) { + return undefined; + } + + if (obj === '') { + return undefined; + } + + const isVpcConfig = obj && typeof obj === 'object' && ('SubnetIds' in obj || 'SecurityGroupIds' in obj); + + if (Array.isArray(obj)) { + const filtered = obj.filter(item => !isEmptyValue(item)); + return filtered.length > 0 ? filtered : undefined; + } + + if (typeof obj === 'object') { + const result = {}; + let hasProperties = false; + + for (const [key, value] of Object.entries(obj)) { + if (isVpcConfig && (key === 'SubnetIds' || key === 'SecurityGroupIds')) { + result[key] = Array.isArray(value) ? value : []; + hasProperties = true; + continue; + } + + if (value === null || value === undefined || value === '') { + continue; + } + + const cleaned = cleanNullKeys(value); + if (cleaned !== undefined) { + result[key] = cleaned; + hasProperties = true; + } + } + + return hasProperties ? result : undefined; + } + + return obj; +} + +function deepEqual(obj1, obj2) { + if (obj1 === null || obj2 === null || typeof obj1 !== 'object' || typeof obj2 !== 'object') { + return obj1 === obj2; + } + + if (Array.isArray(obj1) && Array.isArray(obj2)) { + if (obj1.length !== obj2.length) { + return false; + } + + for (let i = 0; i < obj1.length; i++) { + if (!deepEqual(obj1[i], obj2[i])) { + return false; + } + } + + return true; + } + + if (Array.isArray(obj1) !== Array.isArray(obj2)) { + return false; + } + + const keys1 = Object.keys(obj1); + const keys2 = Object.keys(obj2); + + if (keys1.length !== keys2.length) { + return false; + } + + for (const key of keys1) { + if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) { + return false; + } + } + + return true; +} + +// Helper functions for S3 buckets +function generateS3Key(functionName) { + const date = new Date(); + const timestamp = date.toISOString().replace(/[:.]/g, '-').replace('T', '-').split('Z')[0]; + + let commitHash = ''; + if (process.env.GITHUB_SHA) { + commitHash = `-${process.env.GITHUB_SHA.substring(0, 7)}`; + } + + return `lambda-deployments/${functionName}/${timestamp}${commitHash}.zip`; +} + +async function checkBucketExists(s3Client, bucketName) { + try { + const command = new HeadBucketCommand({ + Bucket: bucketName + }); + await s3Client.send(command); + core.info(`S3 bucket ${bucketName} exists`); + return true; + } catch (error) { + if (error.$metadata?.httpStatusCode === 404 || error.name === 'NotFound') { + core.info(`S3 bucket ${bucketName} does not exist`); + return false; + } + + core.error(`Error checking if bucket exists: ${error.$metadata?.httpStatusCode || error.name} - ${error.message}`); + core.error(`Error details: ${JSON.stringify({ + code: error.code, + name: error.name, + message: error.message, + statusCode: error.$metadata?.httpStatusCode, + requestId: error.$metadata?.requestId + })}`); + + if (error.$metadata?.httpStatusCode === 301) { + core.error(`REGION MISMATCH ERROR: The bucket "${bucketName}" exists but in a different region than specified (${s3Client.config.region}). S3 buckets are global but region-specific. `); + throw new Error(`Bucket "${bucketName}" exists in a different region than ${s3Client.config.region}`); + } else if (error.name === 'AccessDenied' || error.$metadata?.httpStatusCode === 403) { + core.error('Access denied.'); + } + throw error; + } +} + +async function createBucket(s3Client, bucketName, region) { + core.info(`Creating S3 bucket: ${bucketName}`); + + try { + if (!validateBucketName(bucketName)) { + throw new Error(`Invalid bucket name: "${bucketName}". Bucket names must be 3-63 characters, lowercase, start/end with a letter/number, and contain only letters, numbers, dots, and hyphens.`); + } + + const input = { + Bucket: bucketName, + }; + + if (region !== 'us-east-1') { + input.CreateBucketConfiguration = { + LocationConstraint: region + }; + } + + core.info(`Sending CreateBucket request for bucket: ${bucketName} in region: ${region || 'default'}`); + const command = new CreateBucketCommand(input); + + try { + const response = await s3Client.send(command); + core.info(`Successfully created S3 bucket: ${bucketName}`); + core.info(`Bucket location: ${response.Location}`); + + // Apply security configurations after bucket creation + try { + core.info(`Configuring public access block for bucket: ${bucketName}`); + await s3Client.send(new PutPublicAccessBlockCommand({ + Bucket: bucketName, + PublicAccessBlockConfiguration: { + BlockPublicAcls: true, + IgnorePublicAcls: true, + BlockPublicPolicy: true, + RestrictPublicBuckets: true + } + })); + + core.info(`Enabling default encryption for bucket: ${bucketName}`); + await s3Client.send(new PutBucketEncryptionCommand({ + Bucket: bucketName, + ServerSideEncryptionConfiguration: { + Rules: [ + { + ApplyServerSideEncryptionByDefault: { + SSEAlgorithm: 'AES256' + }, + BucketKeyEnabled: true + } + ] + } + })); + + core.info(`Enabling versioning for bucket: ${bucketName}`); + await s3Client.send(new PutBucketVersioningCommand({ + Bucket: bucketName, + VersioningConfiguration: { + Status: 'Enabled' + } + })); + + core.info(`Security configurations successfully applied to bucket: ${bucketName}`); + } catch (securityError) { + core.warning(`Applied partial security settings to bucket. Some security features couldn't be enabled: ${securityError.message}`); + core.debug(securityError.stack); + } + + return true; + } catch (sendError) { + core.error(`Error creating bucket: ${sendError.name} - ${sendError.message}`); + core.error(`Error details: ${JSON.stringify({ + code: sendError.code, + name: sendError.name, + message: sendError.message, + statusCode: sendError.$metadata?.httpStatusCode, + requestId: sendError.$metadata?.requestId + })}`); + + if (sendError.name === 'BucketAlreadyExists' || sendError.name === 'BucketAlreadyOwnedByYou') { + core.error(`Bucket name ${bucketName} is already taken but may be owned by another account.`); + throw sendError; + } else if (sendError.$metadata?.httpStatusCode === 403) { + core.error('Access denied when creating bucket. Check your IAM permissions for s3:CreateBucket.'); + throw new Error(`Access denied when creating bucket ${bucketName}. Ensure your IAM policy includes s3:CreateBucket permission.`); + } else if (sendError.name === 'InvalidBucketName') { + core.error(`The bucket name "${bucketName}" is invalid. See s3-troubleshooting.md for S3 bucket naming rules.`); + } + throw sendError; + } + } catch (error) { + core.error(`Failed to create S3 bucket: ${error.name} - ${error.message}`); + throw error; + } +} + +function validateBucketName(name) { + if (!name || typeof name !== 'string') return false; + + if (name.length < 3 || name.length > 63) return false; + + if (!/^[a-z0-9.-]+$/.test(name)) return false; + + if (!/^[a-z0-9].*[a-z0-9]$/.test(name)) return false; + + if (/^(\d{1,3}\.){3}\d{1,3}$/.test(name)) return false; + + if (/\.\./.test(name)) return false; + + if (/^xn--/.test(name)) return false; + + if (/^sthree-/.test(name)) return false; + + if (/^sthree-configurator/.test(name)) return false; + + if (/^amzn-s3-demo-bucket/.test(name)) return false; + + return true; +} + +async function uploadToS3(zipFilePath, bucketName, s3Key, region) { + core.info(`Uploading Lambda deployment package to S3: s3://${bucketName}/${s3Key}`); + + try { + const s3Client = new S3Client({ + region, + customUserAgent: `LambdaGitHubAction/${version}` + }); + let bucketExists = false; + try { + bucketExists = await checkBucketExists(s3Client, bucketName); + } catch (checkError) { + core.error(`Failed to check if bucket exists: ${checkError.name} - ${checkError.message}`); + core.error(`Error type: ${checkError.name}, Code: ${checkError.code}`); + + if (checkError.$metadata?.httpStatusCode === 403) { + throw new Error(`Access denied to S3 bucket`); + } else { + throw checkError; + } + } + + if (!bucketExists) { + core.info(`Bucket ${bucketName} does not exist. Attempting to create it...`); + try { + await createBucket(s3Client, bucketName, region); + core.info(`Bucket ${bucketName} created successfully.`); + } catch (bucketError) { + core.error(`Failed to create bucket ${bucketName}: ${bucketError.message}`); + core.debug(bucketError.stack || "Bucket error stack trace"); + core.error(`Error details: ${JSON.stringify({ + code: bucketError.code, + name: bucketError.name, + message: bucketError.message, + statusCode: bucketError.$metadata?.httpStatusCode + })}`); + + if (bucketError.name === 'BucketAlreadyExists' || bucketError.name === 'BucketAlreadyOwnedByYou') { + core.info(`Bucket name ${bucketName} is already taken. Please try a different name.`); + } else if (bucketError.$metadata?.httpStatusCode === 403) { + throw new Error(`Access denied when creating bucket. Ensure your IAM policy includes s3:CreateBucket permission.`); + } + throw bucketError; + } + } + + try { + await fs.access(zipFilePath); + core.info(`Deployment package verified at ${zipFilePath}`); + } catch (fileError) { + if (fileError.code === 'EACCES') { + throw new Error(`Permission denied`); + } + throw new Error(`Cannot access deployment package at ${zipFilePath}: ${fileError.message}`); + } + + const fileContent = await fs.readFile(zipFilePath); + core.info(`Read deployment package, size: ${fileContent.length} bytes`); + + try { + + expectedBucketOwner = await getAwsAccountId(region); + + if(!expectedBucketOwner) { + throw new Error("No AWS account ID found."); + } + + const input = { + Bucket: bucketName, + Key: s3Key, + Body: fileContent, + ExpectedBucketOwner: expectedBucketOwner + }; + + core.info(`Sending PutObject request to S3 (bucket: ${bucketName}, key: ${s3Key})`); + const command = new PutObjectCommand(input); + const response = await s3Client.send(command); + + core.info(`S3 upload successful, file size: ${fileContent.length} bytes`); + + return { + bucket: bucketName, + key: s3Key, + ...((response && response.VersionId) ? { versionId: response.VersionId } : {}) + }; + } catch (uploadError) { + core.error(`Failed to upload file to S3: ${uploadError.name} - ${uploadError.message}`); + core.error(`Upload error details: ${JSON.stringify({ + code: uploadError.code, + name: uploadError.name, + message: uploadError.message, + statusCode: uploadError.$metadata?.httpStatusCode, + requestId: uploadError.$metadata?.requestId + })}`); + + if (uploadError.$metadata?.httpStatusCode === 403) { + throw new Error('Access denied when uploading to S3. Ensure your IAM policy includes s3:PutObject permission.'); + } + throw uploadError; + } + + } catch (error) { + core.error(`S3 upload failed: ${error.name} - ${error.message}`); + + if (error.code === 'NoSuchBucket') { + core.error(`Bucket ${bucketName} does not exist and could not be created automatically. Please create it manually or check your permissions.`); + } else if (error.code === 'AccessDenied' || error.name === 'AccessDenied' || error.$metadata?.httpStatusCode === 403) { + core.error('Access denied. Ensure your AWS credentials have the following permissions:'); + core.error('- s3:HeadBucket (to check if the bucket exists)'); + core.error('- s3:CreateBucket (to create the bucket if it doesn\'t exist)'); + core.error('- s3:PutObject (to upload the file to the bucket)'); + core.error('See s3-troubleshooting.md for a complete IAM policy template.'); + } else if (error.name === 'CredentialsProviderError') { + core.error('AWS credentials not found or invalid. Check your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.'); + } else if (error.name === 'InvalidBucketName') { + core.error(`Invalid bucket name: ${bucketName}. Bucket names must follow S3 naming rules.`); + core.error('See s3-troubleshooting.md for S3 bucket naming rules.'); + } + + throw error; + } +} + +// Helper function for retrieving AWS account ID +async function getAwsAccountId(region) { + try { + const stsClient = new STSClient({ + region, + customUserAgent: `LambdaGitHubAction/${version}` + }); + const command = new GetCallerIdentityCommand({}); + const response = await stsClient.send(command); + core.info(`Successfully retrieved AWS account ID: ${response.Account}`); + return response.Account; + } catch (error) { + core.warning(`Failed to retrieve AWS account ID: ${error.message}`); + core.debug(error.stack); + return null; + } +} + +if (require.main === module) { + run(); +} + +module.exports = { + run, + packageCodeArtifacts, + checkFunctionExists, + hasConfigurationChanged, + waitForFunctionUpdated, + waitForFunctionActive, + isEmptyValue, + cleanNullKeys, + deepEqual, + generateS3Key, + uploadToS3, + checkBucketExists, + createBucket, + validateBucketName, + createFunction, + updateFunctionConfiguration, + updateFunctionCode, + getAwsAccountId +}; diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..726ed4c4 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,13 @@ +module.exports = { + clearMocks: true, + collectCoverage: true, + coverageDirectory: 'coverage', + coveragePathIgnorePatterns: ['/node_modules/'], + coverageProvider: 'v8', + coverageReporters: ['json', 'text', 'lcov', 'clover'], + moduleFileExtensions: ['js', 'json', 'node'], + testMatch: ['**/__tests__/**/*.test.js'], + testPathIgnorePatterns: ['/node_modules/'], + verbose: true, + testTimeout: 10000 +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..73cb4555 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,7793 @@ +{ + "name": "@amzn/github-action-lambda-deploy", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@amzn/github-action-lambda-deploy", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@actions/core": "^1.10.0", + "@actions/github": "^5.1.1", + "@aws-sdk/client-lambda": "^3.826.0", + "@aws-sdk/client-s3": "^3.826.0", + "@aws-sdk/client-sts": "3.844.0", + "@aws-sdk/util-retry": "^3.370.0", + "@smithy/node-http-handler": "^4.0.6", + "adm-zip": "^0.5.16", + "glob": "^11.0.2" + }, + "devDependencies": { + "@vercel/ncc": "^0.36.1", + "eslint": "^8.45.0", + "eslint-plugin-jest": "^27.2.2", + "jest": "^29.5.0" + } + }, + "node_modules/@actions/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", + "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==", + "license": "MIT", + "dependencies": { + "@actions/exec": "^1.1.1", + "@actions/http-client": "^2.0.1" + } + }, + "node_modules/@actions/exec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", + "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", + "license": "MIT", + "dependencies": { + "@actions/io": "^1.0.1" + } + }, + "node_modules/@actions/github": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz", + "integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==", + "license": "MIT", + "dependencies": { + "@actions/http-client": "^2.0.1", + "@octokit/core": "^3.6.0", + "@octokit/plugin-paginate-rest": "^2.17.0", + "@octokit/plugin-rest-endpoint-methods": "^5.13.0" + } + }, + "node_modules/@actions/http-client": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", + "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", + "license": "MIT", + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^5.25.4" + } + }, + "node_modules/@actions/io": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", + "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==", + "license": "MIT" + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.826.0.tgz", + "integrity": "sha512-RWkzP6yZ7YZ856ibtab9TYC8zzm7uc0zOcG78moNrIwr3b9yacXrIjRAcRQ6RApYhiVEr4F5CIHyrjgwcDfelQ==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/credential-provider-node": "3.826.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.826.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.821.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.826.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.3", + "@smithy/eventstream-serde-browser": "^4.0.4", + "@smithy/eventstream-serde-config-resolver": "^4.1.2", + "@smithy/eventstream-serde-node": "^4.0.4", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.11", + "@smithy/middleware-retry": "^4.1.12", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.19", + "@smithy/util-defaults-mode-node": "^4.0.19", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "@smithy/util-waiter": "^4.0.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.830.0.tgz", + "integrity": "sha512-Cti+zj1lqvQIScXFQv8/t1xo3pvcvk/ObmGIbyLzfgcYpKMHaIWhzhi6aN+z4dYEv1EwrukC9tNoqScyShc5tw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/credential-provider-node": "3.830.0", + "@aws-sdk/middleware-bucket-endpoint": "3.830.0", + "@aws-sdk/middleware-expect-continue": "3.821.0", + "@aws-sdk/middleware-flexible-checksums": "3.826.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-location-constraint": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-sdk-s3": "3.826.0", + "@aws-sdk/middleware-ssec": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.828.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/signature-v4-multi-region": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.828.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.3", + "@smithy/eventstream-serde-browser": "^4.0.4", + "@smithy/eventstream-serde-config-resolver": "^4.1.2", + "@smithy/eventstream-serde-node": "^4.0.4", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-blob-browser": "^4.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/hash-stream-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/md5-js": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.11", + "@smithy/middleware-retry": "^4.1.12", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.19", + "@smithy/util-defaults-mode-node": "^4.0.19", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "@smithy/util-waiter": "^4.0.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso": { + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.830.0.tgz", + "integrity": "sha512-5zCEpfI+zwX2SIa258L+TItNbBoAvQQ6w74qdFM6YJufQ1F9tvwjTX8T+eSTT9nsFIvfYnUaGalWwJVfmJUgVQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.828.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.828.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.3", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.11", + "@smithy/middleware-retry": "^4.1.12", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.19", + "@smithy/util-defaults-mode-node": "^4.0.19", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.830.0.tgz", + "integrity": "sha512-zeQenzvh8JRY5nULd8izdjVGoCM1tgsVVsrLSwDkHxZTTW0hW/bmOmXfvdaE0wDdomXW7m2CkQDSmP7XdvNXZg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/credential-provider-env": "3.826.0", + "@aws-sdk/credential-provider-http": "3.826.0", + "@aws-sdk/credential-provider-process": "3.826.0", + "@aws-sdk/credential-provider-sso": "3.830.0", + "@aws-sdk/credential-provider-web-identity": "3.830.0", + "@aws-sdk/nested-clients": "3.830.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.830.0.tgz", + "integrity": "sha512-X/2LrTgwtK1pkWrvofxQBI8VTi6QVLtSMpsKKPPnJQ0vgqC0e4czSIs3ZxiEsOkCBaQ2usXSiKyh0ccsQ6k2OA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.826.0", + "@aws-sdk/credential-provider-http": "3.826.0", + "@aws-sdk/credential-provider-ini": "3.830.0", + "@aws-sdk/credential-provider-process": "3.826.0", + "@aws-sdk/credential-provider-sso": "3.830.0", + "@aws-sdk/credential-provider-web-identity": "3.830.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.830.0.tgz", + "integrity": "sha512-+VdRpZmfekzpySqZikAKx6l5ndnLGluioIgUG4ZznrButgFD/iogzFtGmBDFB3ZLViX1l4pMXru0zFwJEZT21Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.830.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/token-providers": "3.830.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.830.0.tgz", + "integrity": "sha512-hPYrKsZeeOdLROJ59T6Y8yZ0iwC/60L3qhZXjapBFjbqBtMaQiMTI645K6xVXBioA6vxXq7B4aLOhYqk6Fy/Ww==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/nested-clients": "3.830.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.828.0.tgz", + "integrity": "sha512-nixvI/SETXRdmrVab4D9LvXT3lrXkwAWGWk2GVvQvzlqN1/M/RfClj+o37Sn4FqRkGH9o9g7Fqb1YqZ4mqDAtA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@smithy/core": "^3.5.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/nested-clients": { + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.830.0.tgz", + "integrity": "sha512-5N5YTlBr1vtxf7+t+UaIQ625KEAmm7fY9o1e3MgGOi/paBoI0+axr3ud24qLIy0NSzFlAHEaxUSWxcERNjIoZw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.828.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.828.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.3", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.11", + "@smithy/middleware-retry": "^4.1.12", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.19", + "@smithy/util-defaults-mode-node": "^4.0.19", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/token-providers": { + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.830.0.tgz", + "integrity": "sha512-aJ4guFwj92nV9D+EgJPaCFKK0I3y2uMchiDfh69Zqnmwfxxxfxat6F79VA7PS0BdbjRfhLbn+Ghjftnomu2c1g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/nested-clients": "3.830.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-endpoints": { + "version": "3.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.828.0.tgz", + "integrity": "sha512-RvKch111SblqdkPzg3oCIdlGxlQs+k+P7Etory9FmxPHyPDvsP1j1c74PmgYqtzzMWmoXTjd+c9naUHh9xG8xg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "@smithy/util-endpoints": "^3.0.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.828.0.tgz", + "integrity": "sha512-LdN6fTBzTlQmc8O8f1wiZN0qF3yBWVGis7NwpWK7FUEzP9bEZRxYfIkV9oV9zpt6iNRze1SedK3JQVB/udxBoA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.828.0", + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.826.0.tgz", + "integrity": "sha512-/FEKnUC3xPkLL4RuRydwzx+y4b55HIX6qLPbGnyIs+sNmCUyc/62ijtV1Ml+b++YzEF6jWNBsJOxeyZdgrJ3Ig==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.826.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.821.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.826.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.3", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.11", + "@smithy/middleware-retry": "^4.1.12", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.19", + "@smithy/util-defaults-mode-node": "^4.0.19", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts": { + "version": "3.844.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.844.0.tgz", + "integrity": "sha512-hPxiovDv4QW+/Rn//U0Z8nbrxj4W7i3OHwZ2qEGey4YFcPISY3GYXqP3ko6x9LkzzWmY1fTdubylJi4MTjZGqQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.844.0", + "@aws-sdk/credential-provider-node": "3.844.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.844.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.844.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.844.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.7.0", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.14", + "@smithy/middleware-retry": "^4.1.15", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.6", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.22", + "@smithy/util-defaults-mode-node": "^4.0.22", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/client-sso": { + "version": "3.844.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.844.0.tgz", + "integrity": "sha512-FktodSx+pfUfIqMjoNwZ6t1xqq/G3cfT7I4JJ0HKHoIIZdoCHQB52x0OzKDtHDJAnEQPInasdPS8PorZBZtHmg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.844.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.844.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.844.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.844.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.7.0", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.14", + "@smithy/middleware-retry": "^4.1.15", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.6", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.22", + "@smithy/util-defaults-mode-node": "^4.0.22", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/core": { + "version": "3.844.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.844.0.tgz", + "integrity": "sha512-pfpI54bG5Xf2NkqrDBC2REStXlDXNCw/whORhkEs+Tp5exU872D5QKguzjPA6hH+8Pvbq1qgt5zXMbduISTHJw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/core": "^3.7.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.6", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-utf8": "^4.0.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.844.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.844.0.tgz", + "integrity": "sha512-WB94Ox86MqcZ4CnRjKgopzaSuZH4hMP0GqdOxG4s1it1lRWOIPOHOC1dPiM0Zbj1uqITIhbXUQVXyP/uaJeNkw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.844.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.844.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.844.0.tgz", + "integrity": "sha512-e+efVqfkhpM8zxYeiLNgTUlX+tmtXzVm3bw1A02U9Z9cWBHyQNb8pi90M7QniLoqRURY1B0C2JqkOE61gd4KNg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.844.0", + "@aws-sdk/types": "3.840.0", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.6", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.844.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.844.0.tgz", + "integrity": "sha512-jc5ArGz2HfAx5QPXD+Ep36+QWyCKzl2TG6Vtl87/vljfLhVD0gEHv8fRsqWEp3Rc6hVfKnCjLW5ayR2HYcow9w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.844.0", + "@aws-sdk/credential-provider-env": "3.844.0", + "@aws-sdk/credential-provider-http": "3.844.0", + "@aws-sdk/credential-provider-process": "3.844.0", + "@aws-sdk/credential-provider-sso": "3.844.0", + "@aws-sdk/credential-provider-web-identity": "3.844.0", + "@aws-sdk/nested-clients": "3.844.0", + "@aws-sdk/types": "3.840.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.844.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.844.0.tgz", + "integrity": "sha512-pUqB0StTNyW0R03XjTA3wrQZcie/7FJKSXlYHue921ZXuhLOZpzyDkLNfdRsZTcEoYYWVPSmyS+Eu/g5yVsBNA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.844.0", + "@aws-sdk/credential-provider-http": "3.844.0", + "@aws-sdk/credential-provider-ini": "3.844.0", + "@aws-sdk/credential-provider-process": "3.844.0", + "@aws-sdk/credential-provider-sso": "3.844.0", + "@aws-sdk/credential-provider-web-identity": "3.844.0", + "@aws-sdk/types": "3.840.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.844.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.844.0.tgz", + "integrity": "sha512-VCI8XvIDt2WBfk5Gi/wXKPcWTS3OkAbovB66oKcNQalllH8ESDg4SfLNhchdnN8A5sDGj6tIBJ19nk+dQ6GaqQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.844.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.844.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.844.0.tgz", + "integrity": "sha512-UNp/uWufGlb5nWa4dpc6uQnDOB/9ysJJFG95ACowNVL9XWfi1LJO7teKrqNkVhq0CzSJS1tCt3FvX4UfM+aN1g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.844.0", + "@aws-sdk/core": "3.844.0", + "@aws-sdk/token-providers": "3.844.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.844.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.844.0.tgz", + "integrity": "sha512-iDmX4pPmatjttIScdspZRagaFnCjpHZIEEwTyKdXxUaU0iAOSXF8ecrCEvutETvImPOC86xdrq+MPacJOnMzUA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.844.0", + "@aws-sdk/nested-clients": "3.844.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.840.0.tgz", + "integrity": "sha512-ub+hXJAbAje94+Ya6c6eL7sYujoE8D4Bumu1NUI8TXjUhVVn0HzVWQjpRLshdLsUp1AW7XyeJaxyajRaJQ8+Xg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-logger": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.840.0.tgz", + "integrity": "sha512-lSV8FvjpdllpGaRspywss4CtXV8M7NNNH+2/j86vMH+YCOZ6fu2T/TyFd/tHwZ92vDfHctWkRbQxg0bagqwovA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.840.0.tgz", + "integrity": "sha512-Gu7lGDyfddyhIkj1Z1JtrY5NHb5+x/CRiB87GjaSrKxkDaydtX2CU977JIABtt69l9wLbcGDIQ+W0uJ5xPof7g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.844.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.844.0.tgz", + "integrity": "sha512-SIbDNUL6ZYXPj5Tk0qEz05sW9kNS1Gl3/wNWEmH+AuUACipkyIeKKWzD6z5433MllETh73vtka/JQF3g7AuZww==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.844.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.844.0", + "@smithy/core": "^3.7.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/nested-clients": { + "version": "3.844.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.844.0.tgz", + "integrity": "sha512-p2XILWc7AcevUSpBg2VtQrk79eWQC4q2JsCSY7HxKpFLZB4mMOfmiTyYkR1gEA6AttK/wpCOtfz+hi1/+z2V1A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.844.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.844.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.844.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.844.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.7.0", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.14", + "@smithy/middleware-retry": "^4.1.15", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.6", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.22", + "@smithy/util-defaults-mode-node": "^4.0.22", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.840.0.tgz", + "integrity": "sha512-Qjnxd/yDv9KpIMWr90ZDPtRj0v75AqGC92Lm9+oHXZ8p1MjG5JE2CW0HL8JRgK9iKzgKBL7pPQRXI8FkvEVfrA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/token-providers": { + "version": "3.844.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.844.0.tgz", + "integrity": "sha512-Kh728FEny0fil+LeH8U1offPJCTd/EDh8liBAvLtViLHt2WoX2xC8rk98D38Q5p79aIUhHb3Pf4n9IZfTu/Kog==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.844.0", + "@aws-sdk/nested-clients": "3.844.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/types": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.840.0.tgz", + "integrity": "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/util-endpoints": { + "version": "3.844.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.844.0.tgz", + "integrity": "sha512-1DHh0WTUmxlysz3EereHKtKoxVUG9UC5BsfAw6Bm4/6qDlJiqtY3oa2vebkYN23yltKdfsCK65cwnBRU59mWVg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-endpoints": "^3.0.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.840.0.tgz", + "integrity": "sha512-JdyZM3EhhL4PqwFpttZu1afDpPJCCc3eyZOLi+srpX11LsGj6sThf47TYQN75HT1CarZ7cCdQHGzP2uy3/xHfQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.844.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.844.0.tgz", + "integrity": "sha512-0eTpURp9Gxbyyeqr78ogARZMSWS5KUMZuN+XMHxNpQLmn2S+J3g+MAyoklCcwhKXlbdQq2aMULEiy0mqIWytuw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.844.0", + "@aws-sdk/types": "3.840.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/@aws-sdk/core": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.826.0.tgz", + "integrity": "sha512-BGbQYzWj3ps+dblq33FY5tz/SsgJCcXX0zjQlSC07tYvU1jHTUvsefphyig+fY38xZ4wdKjbTop+KUmXUYrOXw==", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/core": "^3.5.3", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-utf8": "^4.0.0", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.826.0.tgz", + "integrity": "sha512-DK3pQY8+iKK3MGDdC3uOZQ2psU01obaKlTYhEwNu4VWzgwQL4Vi3sWj4xSWGEK41vqZxiRLq6fOq7ysRI+qEZA==", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.826.0.tgz", + "integrity": "sha512-N+IVZBh+yx/9GbMZTKO/gErBi/FYZQtcFRItoLbY+6WU+0cSWyZYfkoeOxHmQV3iX9k65oljERIWUmL9x6OSQg==", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.826.0.tgz", + "integrity": "sha512-g7n+qSklq/Lzjxe2Ke5QFNCgYn26a3ydZnbFIk8QqYin4pzG+qiunaqJjpV3c/EeHMlfK8bBc7MXAylKzGRccQ==", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/credential-provider-env": "3.826.0", + "@aws-sdk/credential-provider-http": "3.826.0", + "@aws-sdk/credential-provider-process": "3.826.0", + "@aws-sdk/credential-provider-sso": "3.826.0", + "@aws-sdk/credential-provider-web-identity": "3.826.0", + "@aws-sdk/nested-clients": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.826.0.tgz", + "integrity": "sha512-UfIJXxHjmSxH6bea00HBPLkjNI2D04enQA/xNLZvB+4xtzt1/gYdCis1P4/73f5aGVVVB4/zQMobBbnjkrmbQw==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.826.0", + "@aws-sdk/credential-provider-http": "3.826.0", + "@aws-sdk/credential-provider-ini": "3.826.0", + "@aws-sdk/credential-provider-process": "3.826.0", + "@aws-sdk/credential-provider-sso": "3.826.0", + "@aws-sdk/credential-provider-web-identity": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.826.0.tgz", + "integrity": "sha512-kURrc4amu3NLtw1yZw7EoLNEVhmOMRUTs+chaNcmS+ERm3yK0nKjaJzmKahmwlTQTSl3wJ8jjK7x962VPo+zWw==", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.826.0.tgz", + "integrity": "sha512-F19J3zcfoom6OnQ0MyAtvduVKQXPgkz9i5ExSO01J2CzjbyMhCDA99qAjHYe+LwhW+W7P/jzBPd0+uOQ2Nhh9Q==", + "dependencies": { + "@aws-sdk/client-sso": "3.826.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/token-providers": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.826.0.tgz", + "integrity": "sha512-o27GZ6Hy7qhuvMFVUL2eFEpBzf33Jaa/x3u3SHwU0nL7ko7jmbpeF0x4+wmagpI9X2IvVlUxIs0VaQ3YayPLEA==", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/nested-clients": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.830.0.tgz", + "integrity": "sha512-ElVeCReZSH5Ds+/pkL5ebneJjuo8f49e9JXV1cYizuH0OAOQfYaBU9+M+7+rn61pTttOFE8W//qKzrXBBJhfMg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.821.0.tgz", + "integrity": "sha512-zAOoSZKe1njOrtynvK6ZORU57YGv5I7KP4+rwOvUN3ZhJbQ7QPf8gKtFUCYAPRMegaXCKF/ADPtDZBAmM+zZ9g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.826.0.tgz", + "integrity": "sha512-Fz9w8CFYPfSlHEB6feSsi06hdS+s+FB8k5pO4L7IV0tUa78mlhxF/VNlAJaVWYyOkZXl4HPH2K48aapACSQOXw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.821.0.tgz", + "integrity": "sha512-xSMR+sopSeWGx5/4pAGhhfMvGBHioVBbqGvDs6pG64xfNwM5vq5s5v6D04e2i+uSTj4qGa71dLUs5I0UzAK3sw==", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.821.0.tgz", + "integrity": "sha512-sKrm80k0t3R0on8aA/WhWFoMaAl4yvdk+riotmMElLUpcMcRXAd1+600uFVrxJqZdbrKQ0mjX0PjT68DlkYXLg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.821.0.tgz", + "integrity": "sha512-0cvI0ipf2tGx7fXYEEN5fBeZDz2RnHyb9xftSgUsEq7NBxjV0yTZfLJw6Za5rjE6snC80dRN8+bTNR1tuG89zA==", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.821.0.tgz", + "integrity": "sha512-efmaifbhBoqKG3bAoEfDdcM8hn1psF+4qa7ykWuYmfmah59JBeqHLfz5W9m9JoTwoKPkFcVLWZxnyZzAnVBOIg==", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.826.0.tgz", + "integrity": "sha512-8F0qWaYKfvD/de1AKccXuigM+gb/IZSncCqxdnFWqd+TFzo9qI9Hh+TpUhWOMYSgxsMsYQ8ipmLzlD/lDhjrmA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@smithy/core": "^3.5.3", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.821.0.tgz", + "integrity": "sha512-YYi1Hhr2AYiU/24cQc8HIB+SWbQo6FBkMYojVuz/zgrtkFmALxENGF/21OPg7f/QWd+eadZJRxCjmRwh5F2Cxg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.826.0.tgz", + "integrity": "sha512-j404+EcfBbtTlAhyObjXbdKwwDXO1pCxHvR5Fw8FXNvp/H330j6YnXgs3SJ6d3bZUwUJ/ztPx2S5AlBbLVLDFw==", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.821.0", + "@smithy/core": "^3.5.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.826.0.tgz", + "integrity": "sha512-p7olPq0uTtHqGuXI1GSc/gzKDvV55PMbLtnmupEDfnY9SoRu+QatbWQ6da9sI1lhOcNmRMgiNQBXFzaUFrG+SQ==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.826.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.821.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.826.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.3", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.11", + "@smithy/middleware-retry": "^4.1.12", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.19", + "@smithy/util-defaults-mode-node": "^4.0.19", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.821.0.tgz", + "integrity": "sha512-t8og+lRCIIy5nlId0bScNpCkif8sc0LhmtaKsbm0ZPm3sCa/WhCbSZibjbZ28FNjVCV+p0D9RYZx0VDDbtWyjw==", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/service-error-classification": { + "version": "3.370.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.370.0.tgz", + "integrity": "sha512-aHW9Rt6IZtBopCiVL/6SBSTFkyfjjz8FRHD0EYQ15CcySzEXBSuhg2sSRc01ihi+K5CO0FZIMuDOxPyEWIPFig==", + "license": "Apache-2.0", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.826.0.tgz", + "integrity": "sha512-3fEi/zy6tpMzomYosksGtu7jZqGFcdBXoL7YRsG7OEeQzBbOW9B+fVaQZ4jnsViSjzA/yKydLahMrfPnt+iaxg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.826.0.tgz", + "integrity": "sha512-iCOcVAqGPSHtQL8ZBXifZMEcHyUl9wJ8HvLZ5l1ohA/3ZNP+dqEPGi7jfhR5jZKs+xyp2jxByFqfil9PjI9c5A==", + "dependencies": { + "@aws-sdk/core": "3.826.0", + "@aws-sdk/nested-clients": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.821.0.tgz", + "integrity": "sha512-Znroqdai1a90TlxGaJ+FK1lwC0fHpo97Xjsp5UKGR5JODYm7f9+/fF17ebO1KdoBr/Rm0UIFiF5VmI8ts9F1eA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.804.0.tgz", + "integrity": "sha512-wmBJqn1DRXnZu3b4EkE6CWnoWMo1ZMvlfkqU5zPz67xx1GMaXlDCchFvKAXMjk4jn/L1O3tKnoFDNsoLV1kgNQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.821.0.tgz", + "integrity": "sha512-Uknt/zUZnLE76zaAAPEayOeF5/4IZ2puTFXvcSCWHsi9m3tqbb9UozlnlVqvCZLCRWfQryZQoG2W4XSS3qgk5A==", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "@smithy/util-endpoints": "^3.0.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.804.0.tgz", + "integrity": "sha512-zVoRfpmBVPodYlnMjgVjfGoEZagyRF5IPn3Uo6ZvOZp24chnW/FRstH7ESDHDDRga4z3V+ElUQHKpFDXWyBW5A==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-retry": { + "version": "3.370.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.370.0.tgz", + "integrity": "sha512-hpzO+wdFFCvVEBzjEejyH9hNWSFYZ/rXnnrcY1saMbRzxXJDbjF2C4UQ0yLHdwxHJREAn1EwvnGMGktNTa7LXQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/service-error-classification": "3.370.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.821.0.tgz", + "integrity": "sha512-irWZHyM0Jr1xhC+38OuZ7JB6OXMLPZlj48thElpsO1ZSLRkLZx5+I7VV6k3sp2yZ7BYbKz/G2ojSv4wdm7XTLw==", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.826.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.826.0.tgz", + "integrity": "sha512-wHw6bZQWIMcFF/8r03aY9Itp6JLBYY4absGGhCDK1dc3tPEfi8NVSdb05a/Oz+g4TVaDdxLo0OQ/OKMS1DFRHQ==", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.821.0.tgz", + "integrity": "sha512-DIIotRnefVL6DiaHtO6/21DhJ4JZnnIwdNbpwiAhdt/AVbttcE4yw925gsjur0OGv5BTYXQXU3YnANBYnZjuQA==", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", + "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", + "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.4", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.4", + "@babel/types": "^7.27.3", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.5", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.4.tgz", + "integrity": "sha512-Y+bO6U+I7ZKaM5G5rDUZiYfUvQPUibYmAFe7EnKdnKBbVXDZxvp+MWOH5gYciY0EPk4EScsuFMQBbEfpdRKSCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", + "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz", + "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@octokit/auth-token": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", + "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^6.0.3" + } + }, + "node_modules/@octokit/core": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", + "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.3", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/graphql": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", + "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "12.11.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz", + "integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz", + "integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^6.40.0" + }, + "peerDependencies": { + "@octokit/core": ">=2" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "5.16.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz", + "integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^6.39.0", + "deprecation": "^2.3.1" + }, + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/request": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", + "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/request-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "node_modules/@octokit/types": { + "version": "6.41.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", + "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^12.11.0" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.0.0.tgz", + "integrity": "sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.0.0.tgz", + "integrity": "sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.4.tgz", + "integrity": "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.0.tgz", + "integrity": "sha512-7ov8hu/4j0uPZv8b27oeOFtIBtlFmM3ibrPv/Omx1uUdoXvcpJ00U+H/OWWC/keAguLlcqwtyL2/jTlSnApgNQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.0.8", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.3", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", + "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.0.4.tgz", + "integrity": "sha512-7XoWfZqWb/QoR/rAU4VSi0mWnO2vu9/ltS6JZ5ZSZv0eovLVfDfu0/AX4ub33RsJTOth3TiFWSHS5YdztvFnig==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.0.4.tgz", + "integrity": "sha512-3fb/9SYaYqbpy/z/H3yIi0bYKyAa89y6xPmIqwr2vQiUT2St+avRt8UKwsWt9fEdEasc5d/V+QjrviRaX1JRFA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.1.2.tgz", + "integrity": "sha512-JGtambizrWP50xHgbzZI04IWU7LdI0nh/wGbqH3sJesYToMi2j/DcoElqyOcqEIG/D4tNyxgRuaqBXWE3zOFhQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.0.4.tgz", + "integrity": "sha512-RD6UwNZ5zISpOWPuhVgRz60GkSIp0dy1fuZmj4RYmqLVRtejFqQ16WmfYDdoSoAjlp1LX+FnZo+/hkdmyyGZ1w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.0.4.tgz", + "integrity": "sha512-UeJpOmLGhq1SLox79QWw/0n2PFX+oPRE1ZyRMxPIaFEfCqWaqpB7BU9C8kpPOGEhLF7AwEqfFbtwNxGy4ReENA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.1.0.tgz", + "integrity": "sha512-mADw7MS0bYe2OGKkHYMaqarOXuDwRbO6ArD91XhHcl2ynjGCFF+hvqf0LyQcYxkA1zaWjefSkU7Ne9mqgApSgQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.0.4.tgz", + "integrity": "sha512-WszRiACJiQV3QG6XMV44i5YWlkrlsM5Yxgz4jvsksuu7LDXA6wAtypfPajtNTadzpJy3KyJPoWehYpmZGKUFIQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/chunked-blob-reader": "^5.0.0", + "@smithy/chunked-blob-reader-native": "^4.0.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.4.tgz", + "integrity": "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-stream-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.0.4.tgz", + "integrity": "sha512-wHo0d8GXyVmpmMh/qOR0R7Y46/G1y6OR8U+bSTB4ppEzRxd1xVAQ9xOE9hOc0bSjhz0ujCPAbfNLkLrpa6cevg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.4.tgz", + "integrity": "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.0.4.tgz", + "integrity": "sha512-uGLBVqcOwrLvGh/v/jw423yWHq/ofUGK1W31M2TNspLQbUV1Va0F5kTxtirkoHawODAZcjXTSGi7JwbnPcDPJg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.4.tgz", + "integrity": "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.14.tgz", + "integrity": "sha512-+BGLpK5D93gCcSEceaaYhUD/+OCGXM1IDaq/jKUQ+ujB0PTWlWN85noodKw/IPFZhIKFCNEe19PGd/reUMeLSQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.7.0", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.15.tgz", + "integrity": "sha512-iKYUJpiyTQ33U2KlOZeUb0GwtzWR3C0soYcKuCnTmJrvt6XwTPQZhMfsjJZNw7PpQ3TU4Ati1qLSrkSJxnnSMQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/service-error-classification": "^4.0.6", + "@smithy/smithy-client": "^4.4.6", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", + "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", + "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.1.0.tgz", + "integrity": "sha512-vqfSiHz2v8b3TTTrdXi03vNz1KLYYS3bhHCDv36FYDqxT7jvTll1mMnCrkD+gOvgwybuunh/2VmvOMqwBegxEg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", + "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", + "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.6.tgz", + "integrity": "sha512-RRoTDL//7xi4tn5FrN2NzH17jbgmnKidUqd4KvquT0954/i6CXXkh1884jBiunq24g9cGtPBEXlU40W6EpNOOg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.2.tgz", + "integrity": "sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.6.tgz", + "integrity": "sha512-3wfhywdzB/CFszP6moa5L3lf5/zSfQoH0kvVSdkyK2az5qZet0sn2PAHjcTDiq296Y4RP5yxF7B6S6+3oeBUCQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.7.0", + "@smithy/middleware-endpoint": "^4.1.14", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", + "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", + "dependencies": { + "@smithy/querystring-parser": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.22", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.22.tgz", + "integrity": "sha512-hjElSW18Wq3fUAWVk6nbk7pGrV7ZT14DL1IUobmqhV3lxcsIenr5FUsDe2jlTVaS8OYBI3x+Og9URv5YcKb5QA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.6", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.22", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.22.tgz", + "integrity": "sha512-7B8mfQBtwwr2aNRRmU39k/bsRtv9B6/1mTMrGmmdJFKmLAH+KgIiOuhaqfKOBGh9sZ/VkZxbvm94rI4MMYpFjQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.1.4", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.6", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", + "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", + "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.6.tgz", + "integrity": "sha512-+YekoF2CaSMv6zKrA6iI/N9yva3Gzn4L6n35Luydweu5MMPYpiGZlWqehPHDHyNbnyaYlz/WJyYAZnC+loBDZg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.0.6", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.3.tgz", + "integrity": "sha512-cQn412DWHHFNKrQfbHY8vSFI3nTROY1aIKji9N0tpp8gUABRilr7wdf8fqBbSlXresobM+tQFNk6I+0LXK/YZg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.0.5.tgz", + "integrity": "sha512-4QvC49HTteI1gfemu0I1syWovJgPvGn7CVUoN9ZFkdvr/cCFkrEL7qNCdx/2eICqDWEGnnr68oMdSIPCLAriSQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "22.15.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz", + "integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/semver": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", + "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true + }, + "node_modules/@vercel/ncc": { + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.36.1.tgz", + "integrity": "sha512-S4cL7Taa9yb5qbv+6wLgiKVZ03Qfkc4jGRuiUQMQ8HGBD5pcNRnHeYM33zBvJE4/zJGjJJ8GScB+WmTsn9mORw==", + "dev": true, + "bin": { + "ncc": "dist/ncc/cli.js" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/adm-zip": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", + "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", + "engines": { + "node": ">=12.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "license": "Apache-2.0" + }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001721", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz", + "integrity": "sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", + "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "license": "ISC" + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.165", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz", + "integrity": "sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-jest": { + "version": "27.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.9.0.tgz", + "integrity": "sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^5.10.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0 || ^7.0.0", + "eslint": "^7.0.0 || ^8.0.0", + "jest": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.2.tgz", + "integrity": "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ] + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "license": "ISC" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..a6e71c72 --- /dev/null +++ b/package.json @@ -0,0 +1,36 @@ +{ + "name": "@amzn/github-action-lambda-deploy", + "version": "1.0.0", + "description": "GitHub Action for AWS Lambda Function Deployment", + "main": "index.js", + "scripts": { + "build": "ncc build index.js -o dist", + "test": "jest", + "lint": "eslint .", + "lint:fix": "eslint . --fix" + }, + "keywords": [ + "aws", + "lambda", + "deployment" + ], + "author": "", + "license": "MIT", + "dependencies": { + "@actions/core": "^1.10.0", + "@actions/github": "^5.1.1", + "@aws-sdk/client-lambda": "^3.826.0", + "@aws-sdk/client-s3": "^3.826.0", + "@aws-sdk/util-retry": "^3.370.0", + "@smithy/node-http-handler": "^4.0.6", + "@aws-sdk/client-sts": "3.844.0", + "adm-zip": "^0.5.16", + "glob": "^11.0.2" + }, + "devDependencies": { + "@vercel/ncc": "^0.36.1", + "eslint": "^8.45.0", + "eslint-plugin-jest": "^27.2.2", + "jest": "^29.5.0" + } +} diff --git a/validations.js b/validations.js new file mode 100644 index 00000000..fc23ca9d --- /dev/null +++ b/validations.js @@ -0,0 +1,333 @@ +const core = require('@actions/core'); +const path = require('path'); + +function validateNumericInputs() { + const ephemeralStorageInput = core.getInput('ephemeral-storage', { required: false }); + const ephemeralStorage = parseInt(ephemeralStorageInput); + if (ephemeralStorageInput && isNaN(ephemeralStorage)) { + core.setFailed(`Ephemeral storage must be a number, got: ${ephemeralStorageInput}`); + return { valid: false }; + } + + const memorySize = core.getInput('memory-size', { required: false }); + let parsedMemorySize; + if (memorySize !== '') { + parsedMemorySize = parseInt(memorySize); + if (isNaN(parsedMemorySize)) { + core.setFailed(`Memory size must be a number, got: ${memorySize}`); + return { valid: false }; + } + } + + const timeoutInput = core.getInput('timeout', { required: false }); + const timeout = parseInt(timeoutInput); + if (timeoutInput && isNaN(timeout)) { + core.setFailed(`Timeout must be a number, got: ${timeoutInput}`); + return { valid: false }; + } + + return { + valid: true, + ephemeralStorage, + parsedMemorySize, + timeout + }; +} + +function validateRequiredInputs() { + const functionName = core.getInput('function-name', { required: true }); + if (!functionName) { + core.setFailed('Function name must be provided'); + return { valid: false }; + } + + const codeArtifactsDir = core.getInput('code-artifacts-dir'); + if (!codeArtifactsDir) { + core.setFailed('Code-artifacts-dir must be provided'); + return { valid: false }; + } + + let handler = core.getInput('handler', { required: true }); + handler = handler || 'index.handler'; + + let runtime = core.getInput('runtime', { required: true }); + runtime = runtime || 'node20js.x'; + + return { + valid: true, + functionName, + codeArtifactsDir, + handler, + runtime + }; +} + +function validateArnInputs() { + const role = core.getInput('role', { required: false }); + const codeSigningConfigArn = core.getInput('code-signing-config-arn', { required: false }); + const kmsKeyArn = core.getInput('kms-key-arn', { required: false }); + const sourceKmsKeyArn = core.getInput('source-kms-key-arn', { required: false }); + + if (role && !validateRoleArn(role)) { + return { valid: false }; + } + + if (codeSigningConfigArn && !validateCodeSigningConfigArn(codeSigningConfigArn)) { + return { valid: false }; + } + + if (kmsKeyArn && !validateKmsKeyArn(kmsKeyArn)) { + return { valid: false }; + } + + if (sourceKmsKeyArn && !validateKmsKeyArn(sourceKmsKeyArn)) { + return { valid: false }; + } + + return { + valid: true, + role, + codeSigningConfigArn, + kmsKeyArn, + sourceKmsKeyArn + }; +} + +function validateJsonInputs() { + const environment = core.getInput('environment', { required: false }); + const vpcConfig = core.getInput('vpc-config', { required: false }); + const deadLetterConfig = core.getInput('dead-letter-config', { required: false }); + const tracingConfig = core.getInput('tracing-config', { required: false }); + const layers = core.getInput('layers', { required: false }); + const fileSystemConfigs = core.getInput('file-system-configs', { required: false }); + const imageConfig = core.getInput('image-config', { required: false }); + const snapStart = core.getInput('snap-start', { required: false }); + const loggingConfig = core.getInput('logging-config', { required: false }); + const tags = core.getInput('tags', { required: false }); + + let parsedEnvironment, parsedVpcConfig, parsedDeadLetterConfig, parsedTracingConfig, + parsedLayers, parsedFileSystemConfigs, parsedImageConfig, parsedSnapStart, + parsedLoggingConfig, parsedTags; + + try { + if (environment) { + parsedEnvironment = parseJsonInput(environment, 'environment'); + } + + if (vpcConfig) { + parsedVpcConfig = parseJsonInput(vpcConfig, 'vpc-config'); + if (!parsedVpcConfig.SubnetIds || !Array.isArray(parsedVpcConfig.SubnetIds)) { + throw new Error("vpc-config must include 'SubnetIds' as an array"); + } + if (!parsedVpcConfig.SecurityGroupIds || !Array.isArray(parsedVpcConfig.SecurityGroupIds)) { + throw new Error("vpc-config must include 'SecurityGroupIds' as an array"); + } + } + + if (deadLetterConfig) { + parsedDeadLetterConfig = parseJsonInput(deadLetterConfig, 'dead-letter-config'); + if (!parsedDeadLetterConfig.TargetArn) { + throw new Error("dead-letter-config must include 'TargetArn'"); + } + } + + if (tracingConfig) { + parsedTracingConfig = parseJsonInput(tracingConfig, 'tracing-config'); + if (!parsedTracingConfig.Mode || !['Active', 'PassThrough'].includes(parsedTracingConfig.Mode)) { + throw new Error("tracing-config Mode must be 'Active' or 'PassThrough'"); + } + } + + if (layers) { + parsedLayers = parseJsonInput(layers, 'layers'); + if (!Array.isArray(parsedLayers)) { + throw new Error("layers must be an array of layer ARNs"); + } + } + + if (fileSystemConfigs) { + parsedFileSystemConfigs = parseJsonInput(fileSystemConfigs, 'file-system-configs'); + if (!Array.isArray(parsedFileSystemConfigs)) { + throw new Error("file-system-configs must be an array"); + } + for (const config of parsedFileSystemConfigs) { + if (!config.Arn || !config.LocalMountPath) { + throw new Error("Each file-system-config must include 'Arn' and 'LocalMountPath'"); + } + } + } + + if (imageConfig) { + parsedImageConfig = parseJsonInput(imageConfig, 'image-config'); + } + + if (snapStart) { + parsedSnapStart = parseJsonInput(snapStart, 'snap-start'); + if (!parsedSnapStart.ApplyOn || !['PublishedVersions', 'None'].includes(parsedSnapStart.ApplyOn)) { + throw new Error("snap-start ApplyOn must be 'PublishedVersions' or 'None'"); + } + } + + if (loggingConfig) { + parsedLoggingConfig = parseJsonInput(loggingConfig, 'logging-config'); + } + + if (tags) { + parsedTags = parseJsonInput(tags, 'tags'); + if (typeof parsedTags !== 'object' || Array.isArray(parsedTags)) { + throw new Error("tags must be an object of key-value pairs"); + } + } + } catch (error) { + core.setFailed(`Input validation error: ${error.message}`); + return { valid: false }; + } + + return { + valid: true, + environment, + vpcConfig, + deadLetterConfig, + tracingConfig, + layers, + fileSystemConfigs, + imageConfig, + snapStart, + loggingConfig, + tags, + parsedEnvironment, + parsedVpcConfig, + parsedDeadLetterConfig, + parsedTracingConfig, + parsedLayers, + parsedFileSystemConfigs, + parsedImageConfig, + parsedSnapStart, + parsedLoggingConfig, + parsedTags + }; +} + +function getAdditionalInputs() { + const functionDescription = core.getInput('function-description', { required: false }); + const dryRun = core.getBooleanInput('dry-run', { required: false }) || false; + let publish = false; + const revisionId = core.getInput('revision-id', { required: false }); + const architectures = core.getInput('architectures', { required: false }); + const s3Bucket = core.getInput('s3-bucket', { required: false }); + let s3Key = core.getInput('s3-key', { required: false }); + + try { + publish = core.getBooleanInput('publish', { required: false }); + } catch (error) { + publish = false; + } + + const useS3Method = !!s3Bucket; + + return { + functionDescription, + dryRun, + publish, + revisionId, + architectures, + s3Bucket, + s3Key, + useS3Method + }; +} + +function parseJsonInput(jsonString, inputName) { + try { + return JSON.parse(jsonString); + } catch (error) { + throw new Error(`Invalid JSON in ${inputName} input: ${error.message}`); + } +} + +function validateRoleArn(arn) { + const rolePattern = /^arn:aws(-[a-z0-9-]+)?:iam::[0-9]{12}:role\/[a-zA-Z0-9+=,.@_\/-]+$/; + + if (!rolePattern.test(arn)) { + core.setFailed(`Invalid IAM role ARN format: ${arn}`); + return false; + } + return true; +} + +function validateCodeSigningConfigArn(arn) { + const cscPattern = /^arn:aws(-[a-z0-9-]+)?:lambda:[a-z0-9-]+:[0-9]{12}:code-signing-config:[a-zA-Z0-9-]+$/; + + if (!cscPattern.test(arn)) { + core.setFailed(`Invalid code signing config ARN format: ${arn}`); + return false; + } + return true; +} + +function validateKmsKeyArn(arn) { + const kmsPattern = /^arn:aws(-[a-z0-9-]+)?:kms:[a-z0-9-]+:[0-9]{12}:key\/[a-zA-Z0-9-]+$/; + + if (!kmsPattern.test(arn)) { + core.setFailed(`Invalid KMS key ARN format: ${arn}`); + return false; + } + return true; +} + +function validateAndResolvePath(userPath, basePath) { + const normalizedPath = path.normalize(userPath); + const resolvedPath = path.isAbsolute(normalizedPath) ? normalizedPath : path.resolve(basePath, normalizedPath); + const relativePath = path.relative(basePath, resolvedPath); + + if (relativePath && (relativePath.startsWith('..') || path.isAbsolute(relativePath))) { + throw new Error( + `Security error: Path traversal attempt detected. ` + + `The path '${userPath}' resolves to '${resolvedPath}' which is outside the allowed directory '${basePath}'.` + ); + } + return resolvedPath; +} + +function validateAllInputs() { + const requiredInputs = validateRequiredInputs(); + if (!requiredInputs.valid) { + return { valid: false }; + } + + const numericInputs = validateNumericInputs(); + if (!numericInputs.valid) { + return { valid: false }; + } + + const arnInputs = validateArnInputs(); + if (!arnInputs.valid) { + return { valid: false }; + } + + const jsonInputs = validateJsonInputs(); + if (!jsonInputs.valid) { + return { valid: false }; + } + + const additionalInputs = getAdditionalInputs(); + + return { + valid: true, + ...requiredInputs, + ...numericInputs, + ...arnInputs, + ...jsonInputs, + ...additionalInputs + }; +} + +module.exports = { + validateAllInputs, + parseJsonInput, + validateRoleArn, + validateCodeSigningConfigArn, + validateKmsKeyArn, + validateAndResolvePath, + getAdditionalInputs +};