diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 9d99d69427f41e..748b5c9903a038 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1 +1 @@ -FROM ghcr.io/containerbase/devcontainer:10.1.4 +FROM ghcr.io/containerbase/devcontainer:10.6.14 diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh index cdca067449391a..eae5da872d27d6 100755 --- a/.devcontainer/post-create.sh +++ b/.devcontainer/post-create.sh @@ -7,4 +7,4 @@ if [[ "${CODESPACES}" == true ]]; then sudo chmod 1777 /tmp fi -pnpm install +COREPACK_ENABLE_DOWNLOAD_PROMPT=0 pnpm install diff --git a/.eslintrc.js b/.eslintrc.js index fa3c4fd191fd45..87f08fb0cfb5c2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,7 +16,6 @@ module.exports = { 'plugin:@typescript-eslint/recommended-requiring-type-checking', 'plugin:promise/recommended', 'plugin:jest-formatting/recommended', - 'prettier', ], parserOptions: { ecmaVersion: 9, @@ -225,5 +224,14 @@ module.exports = { 'import/extensions': 0, }, }, + { + files: ['tools/docs/test/**/*.mjs'], + env: { + jest: false, + }, + rules: { + '@typescript-eslint/no-floating-promises': 0, + }, + }, ], }; diff --git a/.github/DISCUSSION_TEMPLATE/report-a-problem.yml b/.github/DISCUSSION_TEMPLATE/report-a-problem.yml deleted file mode 100644 index d526271bdd349d..00000000000000 --- a/.github/DISCUSSION_TEMPLATE/report-a-problem.yml +++ /dev/null @@ -1,85 +0,0 @@ -body: - - type: dropdown - id: how-are-you-running-renovate - attributes: - label: How are you running Renovate? - options: - - 'Mend Renovate hosted app on github.com' - - 'Self-hosted Renovate' - - - type: input - id: self-hosted-veresion - attributes: - label: If you're self-hosting Renovate, tell us what version of Renovate you run. - validations: - required: false - - - type: dropdown - id: self-hosted-platform - attributes: - label: If you're self-hosting Renovate, select which platform you are using. - options: - - 'AWS CodeCommit' - - 'Azure DevOps (dev.azure.com)' - - 'Azure DevOps Server' - - 'Bitbucket Cloud (bitbucket.org)' - - 'Bitbucket Server' - - 'Gitea or Forgejo' - - 'github.com' - - 'GitHub Enterprise Server' - - 'gitlab.com' - - 'GitLab self-hosted' - validations: - required: false - - - type: dropdown - id: regression-error - attributes: - label: Was this something which used to work for you, and then stopped? - options: - - 'It used to work, and then stopped' - - 'I have not seen this working' - - - type: textarea - id: describe-problem - attributes: - label: Describe the problem - description: 'Do not report any security concerns here. Email [renovate-disclosure@mend.io](mailto:renovate-disclosure@mend.io) instead.' - validations: - required: true - - - type: textarea - id: debug-logs - attributes: - label: Relevant debug logs - description: | - Try not to report a problem unless you've looked at the logs first. - If you're running self-hosted, run with `LOG_LEVEL=debug` in your environment variables and search for whatever dependency/branch/PR that is causing the problem. - If you are using the Renovate App, log into [Renovate's app dashboard](https://developer.mend.io) and locate the correct job log for when the problem occurred (e.g. when the PR was created). - Try to paste the *relevant* logs here, not the entire thing and not just a link to the dashboard (others don't have permissions to view them). - If you're not sure about the relevant parts of the log, then feel free to post the full log to a [Github Gist](https://gist.github.com/) and link to it. - Try to highlight the important logs into the Discussion itself. - value: | -
Logs - - ``` - - Copy/paste the relevant log(s) here, between the starting and ending backticks - - ``` - -
- validations: - required: false - - - type: dropdown - id: minimal-reproduction-repository - attributes: - label: Have you created a minimal reproduction repository? - description: Please read the [minimal reproductions documentation](https://github.com/renovatebot/renovate/blob/main/docs/development/minimal-reproductions.md) to learn how to make a good minimal reproduction repository. - options: - - 'Placeholder value, please select the correct response from the dropdown' - - 'I have linked to a minimal reproduction in the description above' - - 'I have explained in the description why a minimal reproduction is impossible' - validations: - required: true diff --git a/.github/DISCUSSION_TEMPLATE/ask-a-question.yml b/.github/DISCUSSION_TEMPLATE/request-help.yml similarity index 68% rename from .github/DISCUSSION_TEMPLATE/ask-a-question.yml rename to .github/DISCUSSION_TEMPLATE/request-help.yml index 7c995c9eadff62..3dcb62a21aa156 100644 --- a/.github/DISCUSSION_TEMPLATE/ask-a-question.yml +++ b/.github/DISCUSSION_TEMPLATE/request-help.yml @@ -1,4 +1,13 @@ body: + - type: dropdown + id: question-type + attributes: + label: What would you like help with? + options: + - 'I would like help with my configuration' + - 'I think I found a bug' + - 'Other' + - type: dropdown id: how-are-you-running-renovate attributes: @@ -10,32 +19,14 @@ body: - type: input id: self-hosted-version attributes: - label: If you're self-hosting Renovate, tell us what version of Renovate you run. - validations: - required: false - - - type: dropdown - id: self-hosted-platform - attributes: - label: If you're self-hosting Renovate, select which platform you are using. - options: - - 'AWS CodeCommit' - - 'Azure DevOps (dev.azure.com)' - - 'Azure DevOps Server' - - 'Bitbucket Cloud (bitbucket.org)' - - 'Bitbucket Server' - - 'Gitea or Forgejo' - - 'github.com' - - 'GitHub Enterprise Server' - - 'gitlab.com' - - 'GitLab self-hosted' + label: If you're self-hosting Renovate, tell us which platform (GitHub, GitLab, etc) and which version of Renovate. validations: required: false - type: textarea id: the-question attributes: - label: What is your question? + label: Please tell us more about your question or problem validations: required: true @@ -54,7 +45,7 @@ body: ``` - Copy/paste the relevant log(s) here, between the starting and ending backticks + Replace this text with your logs, between the starting and ending triple backticks ``` diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 4fc4b36e860ad1..1aa7347f696af0 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: false contact_links: - name: Start a discussion - url: https://github.com/renovatebot/renovate/discussions/new + url: https://github.com/renovatebot/renovate/discussions/new/choose about: Our preferred starting point if you have any questions or suggestions about bot configuration, features or behavior. diff --git a/.github/actions/calculate-prefetch-matrix/action.yml b/.github/actions/calculate-prefetch-matrix/action.yml index b58c6353d6fd8b..c895efeaef0cce 100644 --- a/.github/actions/calculate-prefetch-matrix/action.yml +++ b/.github/actions/calculate-prefetch-matrix/action.yml @@ -34,7 +34,7 @@ runs: - name: Check cache miss for MacOS id: macos-cache - uses: actions/cache/restore@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: node_modules key: ${{ env.MACOS_KEY }} @@ -43,7 +43,7 @@ runs: - name: Check cache miss for Windows id: windows-cache - uses: actions/cache/restore@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: node_modules key: ${{ env.WINDOWS_KEY }} diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml index 2c24caa22ca310..b0b6e2bc360ac6 100644 --- a/.github/actions/setup-node/action.yml +++ b/.github/actions/setup-node/action.yml @@ -34,7 +34,7 @@ runs: - name: Restore `node_modules` id: node-modules-restore - uses: actions/cache/restore@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: node_modules key: ${{ env.CACHE_KEY }} @@ -47,9 +47,10 @@ runs: (steps.node-modules-restore.outputs.cache-hit == 'true') && 'true' || '' }}' >> "$GITHUB_ENV" - - name: Enable corepack - shell: bash - run: corepack enable + - name: Setup pnpm + uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d # v3.0.0 + with: + standalone: true - name: Setup Node uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -67,7 +68,7 @@ runs: - name: Write `node_modules` cache if: inputs.save-cache == 'true' && env.CACHE_HIT != 'true' - uses: actions/cache/save@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + uses: actions/cache/save@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: node_modules key: ${{ env.CACHE_KEY }} diff --git a/.github/contributing.md b/.github/contributing.md index d041337e30d2fc..20bdc16f0416ae 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -21,15 +21,6 @@ If you want help with your Renovate configuration, go to the [discussions tab in For **feature requests**: first search for related requests in the issues and discussions, if you don't find anything: create a _discussion_. -## Rate Limiting of Support Requests through Temporary Blocking - -To ensure that the Renovate maintainers don't burn out from dealing with unfriendly behavior, those who display a bad attitude when asking for or receiving support in the repo will be rate limited from further requests through the use of temporary blocking. -The duration of the temporary block depends on how rude or inconsiderate the behavior is perceived to be, and can be from 1-30 days. - -If you have been blocked temporarily and believe that it is due to a misunderstanding, or you regret your comments and wish to make amends, please reach out to the lead maintainer Rhys Arkins by email with any request for early unblocking. -If/once you are unblocked, you should edit or delete whatever comment lead to the blocking, even if you did not intend it to be rude or inconsiderate. -Long emails or apologies are undesirable - the maintainers are busy and want to be able to help as many users as possible with the time they have available. - ## Code If you would like to fix a bug or work on a feature, please fork the repository and create a Pull Request. diff --git a/.github/label-actions.yml b/.github/label-actions.yml index b46ba40aebc2f3..6f8463a1df654f 100644 --- a/.github/label-actions.yml +++ b/.github/label-actions.yml @@ -9,15 +9,12 @@ Before we start working on your issue we need to know exactly what's causing the current behavior. A minimal reproduction helps us with this. + Discussions without reproductions are less likely to be converted to Issues. To get started, please read our guide on creating a [minimal reproduction](https://github.com/renovatebot/renovate/blob/main/docs/development/minimal-reproductions.md). - We may close the discussion if you, or someone else, haven't created a minimal reproduction within two weeks. - If you need more time, or are stuck, please ask for help or more time in a comment. - - Good luck, @@ -42,15 +39,15 @@
Select me to read instructions - If you use the Renovate app (GitHub): + If you use the Mend Renovate app (GitHub): - 1. Go to the affected PR, and search for "View repository job log here" + 1. Log in to [the Mend Developer Portal](https://developer.mend.io/) - 1. Select the link to go to the "Mend Renovate Dashboard" and log in + 1. Navigate to the correct organization and repository - 1. You are now in the correct repository log overview screen + 1. Locate the appropriate log (it may not always be the latest one) - 1. Copy/paste the correct log + 1. Copy/paste the log contents 1. Follow the steps in the **formatting your logs** section @@ -62,7 +59,7 @@
Select me to read instructions - If you're running self-hosted, run with `LOG_LEVEL=debug` in your environment variables and search for whatever dependency/branch/PR that is causing the problem. + Read the [Renovate docs, troubleshooting, self-hosted](https://docs.renovatebot.com/troubleshooting/#self-hosted) to learn how to find the logs.
@@ -96,6 +93,9 @@
+ If you feel the logs are too large to paste here, please use a service like [GitHub Gist](https://gist.github.com/) and paste the link here. + + Good luck, @@ -128,6 +128,9 @@ Please try the latest version and tell us if that fixes your problem. + Be sure to provide updated logs once you have run with a newer version. + + Good luck, @@ -168,7 +171,7 @@ Hi there, - You are using `done` comments which cause a lot of noise. + You are using `done` comments which cause a lot of noise/notifications. Instead, please use GitHub's web interface to request another review. Please read our [contributing guidelines](https://github.com/renovatebot/renovate/blob/main/.github/contributing.md#resolve-review-comments-instead-of-commenting) to reduce noise. @@ -184,7 +187,13 @@ Thank you for your PR, but we need to discuss the requirements and implementation first. - This PR will be closed, but you can reopen it after the discussion has been resolved. + + + The maintainers believe that there is a lack of - or misalignment of - requirements about this PR. + We need to discuss the requirements and implementation first so that you don't write code that won't be merged. + + + This PR will be closed for now to avoid confusion, but you can reopen it after the discussion has been resolved. Thanks, the Renovate team @@ -198,21 +207,39 @@ This discussion is missing some details, making it difficult or impossible to help you. Please try again to provide more details. + + For example, you may have left out information about your platform (e.g. GitHub Enterprise Server, etc), your version of Renovate (npm, Docker, GitHub Action, etc), or how you're running Renovate. + + + If you can't think of what possible information might be required, please reply to this message and ask for help. + 'needs-discussion': - unlabel: - - 'type:bug' - - 'type:feature' - - 'status:requirements' comment: > **Please create a GitHub Discussion instead of this issue.** - We only want Renovate maintainers to create new Issues. If needed, a Renovate maintainer will create an Issue after your Discussion been triaged and confirmed. As a Renovate user, please create a GitHub Discussion in this repo instead. + Issues in this repository are for creation by Maintainers only - please create a GitHub Discussion instead. + If needed, a Renovate maintainer will create an Issue after your Discussion been triaged and confirmed. This Issue will now be closed and locked. We may later batch-delete this issue. This way we keep Issues actionable, and free of duplicates or wrong bug reports. + Thanks, the Renovate team + close: true + close-reason: 'not planned' + +'auto:inactivity-pr-close': + comment: > + **We're closing this PR due to inactivity, but we are happy for you, or others, to finish the PR.** + + + We limit the number of open PRs, so we close stale PRs, or PRs that are not getting ready to merge. + + + If you, or someone else, want to continue working on this PR, then please reopen this PR and let us know. + + Thanks, the Renovate team close: true close-reason: 'not planned' @@ -271,9 +298,6 @@ - Stop giving off more bad vibes - If you're unhappy with this, we suggest you stop using the repository discussions or the product altogether. - - Thanks, the Renovate team 'auto:one-topic': @@ -299,32 +323,13 @@ Hi there, - Please do not unnecessarily `@` mention maintainers like `@rarkins` or `@viceice`. Doing so causes annoying notifications and makes it harder to maintain this repository. - - - For example, never `@` mention a maintainer when you are creating a discussion if your desire is to get attention. This is rude behavior, just like shouting out your coffee order in a Starbucks before it's your turn. - - - It's OK to comment in an issue or discussion after multiple days or weeks. But please, still don't `@` mention people. The maintainers try to answer most discussions, but they can't answer all discussions. If you're still not getting an answer, take a look at the information you've given us and see if you can improve it. - - - Thanks, the Renovate team - -'auto:misclassified-problem': - comment: > - Hi there, - + This is intended as a polite, automated _request_ that users avoid `@` mentioning repository maintainers like `@rarkins` or `@viceice`. Doing so causes annoying mobile notifications and makes it harder to maintain this repository. - A maintainer has flagged that this discussion is _misclassified_ as a bug when it is not. + We know it might be common elsewhere but we participate in hundreds of discussions a week and would need to turn off GitHub mobile notifications if we were mentioned in every one. - Incorrectly classified discussions waste maintainer time, worsen search result accuracy and make it harder to train AI on this dataset. + As a general rule, we will read and respond to all discussions in this repository, so there is no need to mention us. - The next time you create a discussion, please keep in mind: - - If you are new to Renovate, try to stick to questions instead of problem reports - - Just because Renovate does something you don't expect, doesn't automatically mean it's a bug - - Unsupported features should be raised as ideas, not problems - - Those who appear to be _twisting_ questions into sounding like a bug for attention will be given the least support Thanks, the Renovate team diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 13269db28c7b70..071b8a739b6b02 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,13 +32,13 @@ env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} NODE_VERSION: 18 DRY_RUN: true + TEST_LEGACY_DECRYPTION: true SPARSE_CHECKOUT: |- .github/actions/ data/ tools/ package.json pnpm-lock.yaml - codecov.yml jobs: setup: @@ -93,10 +93,11 @@ jobs: run: gh api ${{ env.PR_URL }} | jq -rc '${{ env.JQ_FILTER }}' >> "$GITHUB_OUTPUT" - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: - sparse-checkout: ${{ env.SPARSE_CHECKOUT }} filter: blob:none # we don't need all blobs + sparse-checkout: ${{ env.SPARSE_CHECKOUT }} + show-progress: false - name: Calculate matrix for `node_modules` prefetch uses: ./.github/actions/calculate-prefetch-matrix @@ -150,9 +151,11 @@ jobs: steps: - name: Checkout code if: needs.setup.outputs.os-matrix-is-full && runner.os != 'Linux' - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: + filter: blob:none # we don't need all blobs sparse-checkout: ${{ env.SPARSE_CHECKOUT }} + show-progress: false - name: Setup Node.js if: needs.setup.outputs.os-matrix-is-full && runner.os != 'Linux' @@ -172,7 +175,9 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + with: + show-progress: false - name: Setup Node.js uses: ./.github/actions/setup-node @@ -181,7 +186,7 @@ jobs: os: ${{ runner.os }} - name: Restore eslint cache - uses: actions/cache/restore@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: .cache/eslint key: eslint-main-cache @@ -200,7 +205,7 @@ jobs: - name: Save eslint cache if: github.event_name == 'push' - uses: actions/cache/save@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + uses: actions/cache/save@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: .cache/eslint key: eslint-main-cache @@ -215,7 +220,9 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + with: + show-progress: false - name: Setup Node.js uses: ./.github/actions/setup-node @@ -224,7 +231,7 @@ jobs: os: ${{ runner.os }} - name: Restore prettier cache - uses: actions/cache/restore@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: .cache/prettier key: prettier-main-cache @@ -243,7 +250,7 @@ jobs: - name: Save prettier cache if: github.event_name == 'push' - uses: actions/cache/save@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + uses: actions/cache/save@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: .cache/prettier key: prettier-main-cache @@ -255,7 +262,9 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + with: + show-progress: false - name: Setup Node.js uses: ./.github/actions/setup-node @@ -264,7 +273,7 @@ jobs: os: ${{ runner.os }} - name: Lint markdown - uses: DavidAnson/markdownlint-cli2-action@510b996878fc0d1a46c8a04ec86b06dbfba09de7 # v15.0.0 + uses: DavidAnson/markdownlint-cli2-action@b4c9feab76d8025d1e83c653fa3990936df0e6c8 # v16.0.0 - name: Lint fenced code blocks run: pnpm doc-fence-check @@ -282,7 +291,9 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + with: + show-progress: false - name: Setup Node.js uses: ./.github/actions/setup-node @@ -319,7 +330,9 @@ jobs: include: ${{ fromJSON(needs.setup.outputs.test-shard-matrix) }} steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + with: + show-progress: false - name: Setup Node.js uses: ./.github/actions/setup-node @@ -328,7 +341,7 @@ jobs: os: ${{ runner.os }} - name: Cache jest - uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: .cache/jest key: | @@ -366,7 +379,7 @@ jobs: - name: Save coverage artifacts if: (success() || failure()) && github.event.pull_request.draft != true && matrix.coverage - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: ${{ matrix.upload-artifact-name }} path: | @@ -377,20 +390,27 @@ jobs: needs: [test] runs-on: ubuntu-latest timeout-minutes: 3 - if: (success() || failure()) && github.event.pull_request.draft != true + if: (success() || failure()) && github.event_name != 'merge_group' && github.event.pull_request.draft != true steps: + - name: Checkout code + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + with: + filter: blob:none # we don't need all blobs + show-progress: false + - name: Download coverage reports - uses: actions/download-artifact@87c55149d96e628cc2ef7e6fc2aab372015aec85 # v4.1.3 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: pattern: coverage-* path: coverage merge-multiple: true - name: Codecov - uses: codecov/codecov-action@ab904c41d6ece82784817410c45d8b8c02684457 # v3.1.6 + uses: codecov/codecov-action@125fc84a9a348dbcf27191600683ec096ec9021c # v4.4.1 with: + token: ${{ secrets.CODECOV_TOKEN }} directory: coverage/lcov - fail_ci_if_error: true + fail_ci_if_error: github.event_name != 'pull_request' verbose: true coverage-threshold: @@ -401,10 +421,11 @@ jobs: if: (success() || failure()) && github.event.pull_request.draft != true steps: - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: - sparse-checkout: ${{ env.SPARSE_CHECKOUT }} filter: blob:none # we don't need all blobs + sparse-checkout: ${{ env.SPARSE_CHECKOUT }} + show-progress: false - name: Setup Node.js uses: ./.github/actions/setup-node @@ -413,7 +434,7 @@ jobs: os: ${{ runner.os }} - name: Download coverage reports - uses: actions/download-artifact@87c55149d96e628cc2ef7e6fc2aab372015aec85 # v4.1.3 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: pattern: coverage-* path: coverage @@ -490,7 +511,9 @@ jobs: if: github.event.pull_request.draft != true steps: - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + with: + show-progress: false - name: Setup Node.js uses: ./.github/actions/setup-node @@ -510,7 +533,7 @@ jobs: run: pnpm test-e2e:pack - name: Upload - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: renovate-package path: renovate-0.0.0-semantic-release.tgz @@ -522,7 +545,9 @@ jobs: if: github.event.pull_request.draft != true steps: - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + with: + show-progress: false - name: Setup Node.js uses: ./.github/actions/setup-node @@ -533,8 +558,11 @@ jobs: - name: Build run: pnpm build:docs + - name: Test docs + run: pnpm test:docs + - name: Upload - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: docs path: tmp/docs/ @@ -548,19 +576,22 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + with: + show-progress: false + + - name: Setup pnpm + uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d # v3.0.0 + with: + standalone: true - name: Setup Node.js uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: ${{ env.NODE_VERSION }} - - name: Enable corepack - shell: bash - run: corepack enable - - name: Download package - uses: actions/download-artifact@87c55149d96e628cc2ef7e6fc2aab372015aec85 # v4.1.3 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: renovate-package @@ -594,14 +625,14 @@ jobs: packages: write steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: fetch-depth: 0 # zero stands for full checkout, which is required for semantic-release - show-progress: false filter: blob:none # we don't need all blobs, only the full tree + show-progress: false - name: docker-config - uses: containerbase/internal-tools@dc264f478d5abd1fb9e28e29dc3becb0ad57b5a2 # v3.0.61 + uses: containerbase/internal-tools@a0551836e0d8c9de0562e344da3c3832a03b9742 # v3.0.88 with: command: docker-config @@ -611,7 +642,7 @@ jobs: node-version: ${{ env.NODE_VERSION }} os: ${{ runner.os }} - - uses: sigstore/cosign-installer@e1523de7571e31dbe865fd2e80c5c7c23ae71eb4 # v3.4.0 + - uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0 - name: Docker registry login run: | diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 2e4e14b462a60b..4dcc77c1d9b02d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -31,7 +31,9 @@ jobs: security-events: write steps: - name: Checkout repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + with: + show-progress: false - name: Delete fixtures to suppress false positives run: | @@ -39,7 +41,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 + uses: github/codeql-action/init@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6 with: languages: javascript @@ -49,7 +51,7 @@ jobs: # 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@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 + uses: github/codeql-action/autobuild@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -63,4 +65,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 + uses: github/codeql-action/analyze@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6 diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index cb3735c36617f6..a2dce1d14fee81 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -9,7 +9,9 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + with: + show-progress: false - name: 'Dependency Review' - uses: actions/dependency-review-action@9129d7d40b8c12c1ed0f60400d00c92d437adcce # v4.1.3 + uses: actions/dependency-review-action@0c155c5e8556a497adf53f2c18edabf945ed8e70 # v4.3.2 diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index e71f5b0cdb0a6f..6a7b6bccd8ff37 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -18,9 +18,11 @@ jobs: if: github.event.pull_request.draft != true steps: - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + with: + show-progress: false - name: Build and run dev container task - uses: devcontainers/ci@3d462823359c481c587cb7426f39775f24257115 # v0.3.1900000339 + uses: devcontainers/ci@a56d055efecd725e8cfe370543b6071b79989cc8 # v0.3.1900000349 with: runCmd: pnpm build diff --git a/.github/workflows/mend-slack.yml b/.github/workflows/mend-slack.yml index 2f224e3f8d04ae..0392ebdfbc324d 100644 --- a/.github/workflows/mend-slack.yml +++ b/.github/workflows/mend-slack.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Post to Slack id: slack - uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.0 + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0 with: channel-id: 'C05NLTMGCJC' # For posting a simple plain text message diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 37c22d1545e5c0..30c892887ac311 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -20,12 +20,13 @@ jobs: steps: - name: 'Checkout code' - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: persist-credentials: false + show-progress: false - name: 'Run analysis' - uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 + uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3 with: results_file: results.sarif results_format: sarif @@ -42,7 +43,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: 'Upload artifact' - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: SARIF file path: results.sarif @@ -50,6 +51,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 + uses: github/codeql-action/upload-sarif@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6 with: sarif_file: results.sarif diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 814dad582d5ca3..90336eab0b437a 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -21,7 +21,7 @@ jobs: - full steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: show-progress: false @@ -31,7 +31,7 @@ jobs: format: 'sarif' output: 'trivy-results.sarif' - - uses: github/codeql-action/upload-sarif@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 + - uses: github/codeql-action/upload-sarif@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6 with: sarif_file: trivy-results.sarif category: 'docker-image-${{ matrix.tag }}' diff --git a/.github/workflows/update-data.yml b/.github/workflows/update-data.yml index 2c962655f340d8..e0b7062f098b24 100644 --- a/.github/workflows/update-data.yml +++ b/.github/workflows/update-data.yml @@ -17,11 +17,14 @@ jobs: contents: write pull-requests: write steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + with: + show-progress: false - - name: Enable corepack - shell: bash - run: corepack enable + - name: Setup pnpm + uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d # v3.0.0 + with: + standalone: true - name: Set up Node.js ${{ env.NODE_VERSION }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -39,7 +42,7 @@ jobs: run: pnpm prettier-fix - name: Create pull request - uses: peter-evans/create-pull-request@a4f52f8033a6168103c2538976c07b467e8163bc # v6.0.1 + uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e # v6.0.5 with: author: 'Renovate Bot ' branch: 'chore/update-static-data' diff --git a/.github/workflows/ws_scan.yaml b/.github/workflows/ws_scan.yaml index 8f6f1052db257e..ca0cc9d2148c7e 100644 --- a/.github/workflows/ws_scan.yaml +++ b/.github/workflows/ws_scan.yaml @@ -11,7 +11,9 @@ jobs: WS_SCAN: runs-on: ubuntu-latest steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + with: + show-progress: false - name: Download UA run: curl -LJO https://github.com/whitesource/unified-agent-distribution/releases/latest/download/wss-unified-agent.jar diff --git a/.ls-lint.yml b/.ls-lint.yml index bfa1b8661035a2..f6771f74f0e55f 100644 --- a/.ls-lint.yml +++ b/.ls-lint.yml @@ -12,6 +12,7 @@ ignore: - .git - .github/ISSUE_TEMPLATE - .github/pull_request_template.md + - CODE_OF_CONDUCT.md - dist - jest.config.ts - node_modules diff --git a/.npmrc b/.npmrc index 58d12ff40c5146..4cda5a4d01298e 100644 --- a/.npmrc +++ b/.npmrc @@ -5,3 +5,4 @@ provenance = true # https://pnpm.io/cli/run shell-emulator = true enable-pre-post-scripts = true +strict-peer-dependencies = true diff --git a/.nvmrc b/.nvmrc index 3c5535cf60a0e8..87834047a6fa65 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18.19.1 +20.12.2 diff --git a/.vscode/launch.json b/.vscode/launch.json index 0e883005baee3b..998732a9141ace 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -31,7 +31,7 @@ "request": "launch", "name": "Jest Current File", "runtimeExecutable": "pnpm", - "program": "jest:vscode", + "program": "jest", "args": [ "--runInBand", "--collectCoverage=false", @@ -52,7 +52,7 @@ "request": "launch", "name": "Jest All", "runtimeExecutable": "pnpm", - "program": "jest:vscode", + "program": "jest", "args": [ "--runInBand", "--collectCoverage=false", @@ -71,7 +71,7 @@ "request": "launch", "name": "Jest Current Folder", "runtimeExecutable": "pnpm", - "program": "jest:vscode", + "program": "jest", "args": [ "--runInBand", "--collectCoverage=false", @@ -84,14 +84,29 @@ }, { "type": "node", - "name": "vscode-jest-tests", + "name": "vscode-jest-tests.v2", "request": "launch", "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen", + "internalConsoleOptions": "openOnSessionStart", "cwd": "${workspaceFolder}", - "runtimeExecutable": "pnpm", - "program": "jest:vscode", - "args": ["--runInBand", "--watchAll=false", "--testTimeout=100000000"] + "runtimeExecutable": "node", + "runtimeArgs": ["--experimental-vm-modules"], + "program": "node_modules/jest/bin/jest.js", + "args": [ + "--runInBand", + "--watchAll=false", + "--testTimeout=100000000", + "--coverage=false", + "--runTestsByPath", + "${jest.testFile}", + "--testNamePattern", + "${jest.testNamePattern}" + ], + "env": { + "NODE_ENV": "test", + "LOG_LEVEL": "trace", + "GIT_ALLOW_PROTOCOL": "file" + } } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 9e9e9e57616939..9a5cba35c8c111 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,8 +16,13 @@ ".releaserc": "json" }, "omnisharp.autoStart": false, - "jest.autoRun": "off", - "jest.jestCommandLine": "pnpm jest", + "jest.runMode": "on-demand", + "jest.jestCommandLine": "node --experimental-vm-modules node_modules/jest/bin/jest.js", + "jest.nodeEnv": { + "NODE_ENV": "test", + "LOG_LEVEL": "trace", + "GIT_ALLOW_PROTOCOL": "file" + }, "npm.packageManager": "pnpm", "editor.formatOnSave": true, "editor.codeActionsOnSave": { diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000000000..4be7f647231a35 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,105 @@ +# Code of Conduct + +To help us deliver great features and support the Renovate Open Source project we ask that you: + +- are polite +- pay attention to details +- keep in mind that most maintainers are volunteers +- are respectful of the time and effort of the maintainers + +## Our priorities + +We want to keep this project sustainable. +This means we support our maintainers and contributors, who spend their free time to help others. + +Maintainers getting stressed is a big threat to Open Source projects, like ours. +Stressed maintainers quit, or reduce their time spent on the project. +Often a few users behave badly, where most users are nice. + +We want to avoid maintainers getting stressed out by bad behavior from contributors. +That's why we have these rules. + +## Politeness + +Sadly, it's common in Open Source projects for a few users to behave in an aggressive and rude way. +A user might say something like: "You should have fixed this bug already!", or "Why am I still waiting for this feature?". +We do not allow this kind of behavior. + +We expect basic politeness, do not act rude. +For example: it is okay if you ask a question and do not thank us afterwards. +But avoid writing mean comments like: "Pity the documentation didn’t say that." or "Thanks for nothing.". + +## Respect the time of those who help you + +Respect goes both ways, but time is limited. +When you ask for help, please remember that the maintainer's time is valuable. +We get many questions each week and do our best to answer each one. +To get the help you need, please be prepared to give detailed logs or descriptions of your issues. +If you do not want to spend the effort giving us enough information, it's likely you will not get the help you need. + +Remember, most of the support provided by our team, including the Mend.io staff, is _unpaid_. + +## Blocking and unblocking + +We quickly deal with rudeness in the community with: + +- automated comments +- temporary blocks +- permanent bans + +If you keep breaking the rules or challenge our guidelines openly, you will be blocked. +For example: if you keep spamming the maintainers with `@mentions` or challenge our rules openly, you will be blocked. + +We generally do not argue about these decisions, but we are willing to reverse a block if you show that you understand and respect the rules, or if there was a misunderstanding. +To reverse a block, or to clear up a misunderstanding, write a _short_ email to Renovate's lead maintainer Rhys Arkins. + +Simply put: we block and unblock swiftly, what matters is how you follow the rules going forward. + +## How we prioritize work + +Renovate's core contributors and maintainers focus on work that: + +- Helps a lot of users, or +- Fixes regressions (errors introduced by recent changes), or +- Is required by a customer of Mend.io, or +- Is sponsored by third parties after independent validation, or +- We personally need or want to implement + +You may be disappointed when we focus on other work ahead of your feature or bug, but you should understand and accept this. + +## Maintaining Issue and Code quality + +We use GitHub Discussions to start and sort issues. +Only maintainers are allowed to create new issues. +If we confirm a bug or agree with a feature idea, and if it's well-documented, we will turn it into an official issue. +This way most issues are ready to work on, either by us or the community. + +We may reject ideas that are too specialized, or that would make the project too hard to maintain. + +We have strict coding standards and reviews to keep our code in good shape. +A feature or fix must of course work, but it must also be well designed to stay maintainable. +We may ask you to improve your code several times in a row, which can be difficult for you. +We only do this to keep the project sustainable. + +## If you have urgent work + +People working for big companies might push too hard in Open Source projects. +It’s often hard for them to understand that our maintainers cannot spend much time to solve their issues quickly. +Frequent requests for updates like "@rarkins how can we move this forward?" are _not_ helpful. + +Please remember, unless you are a Mend.io customer, this project does not owe you the level of response or support you might expect. +Mend.io customers should use their designated support channels for urgent needs. + +## Getting more help + +If you need more assistance than what this project offers, you have two options: + +1. Become a Mend.io customer, such as by buying Renovate Enterprise, or +1. Hire an experienced Renovate contributor privately for consulting. Mend.io staff do _not_ offer this service, but one of our volunteer maintainers, [`@secustor`](https://github.com/secustor), does + +## Feedback + +We welcome respectful discussions about these rules and accept suggestions that improve this text. +We avoid debates on social media or going off-topic in GitHub Discussions. + +Because we enforce all these rules, we can deliver new features and give excellent support to the community. diff --git a/data/kubernetes-api.json5 b/data/kubernetes-api.json5 index 19aa2e192e6307..81a6df5a0b27d3 100644 --- a/data/kubernetes-api.json5 +++ b/data/kubernetes-api.json5 @@ -130,17 +130,25 @@ HelmChart: [ 'source.toolkit.fluxcd.io/v1alpha1', 'source.toolkit.fluxcd.io/v1beta1', + 'source.toolkit.fluxcd.io/v1', ], HelmRelease: [ 'helm.toolkit.fluxcd.io/v2beta1', 'helm.toolkit.fluxcd.io/v2beta2', + 'helm.toolkit.fluxcd.io/v2', ], HelmRepository: [ 'source.toolkit.fluxcd.io/v1alpha1', 'source.toolkit.fluxcd.io/v1beta1', 'source.toolkit.fluxcd.io/v1beta2', + 'source.toolkit.fluxcd.io/v1', ], + ImagePolicy: ['image.toolkit.fluxcd.io/v1beta2'], ImageRepository: ['image.toolkit.fluxcd.io/v1beta2'], + ImageUpdateAutomation: [ + 'image.toolkit.fluxcd.io/v1beta1', + 'image.toolkit.fluxcd.io/v1beta2' + ], OCIRepository: ['source.toolkit.fluxcd.io/v1beta2'], Provider: [ 'notification.toolkit.fluxcd.io/v1beta2', diff --git a/data/node-js-schedule.json b/data/node-js-schedule.json index c1cf00ead26428..e2e1b481c8891d 100644 --- a/data/node-js-schedule.json +++ b/data/node-js-schedule.json @@ -120,7 +120,7 @@ "end": "2024-06-01" }, "v22": { - "start": "2024-04-23", + "start": "2024-04-24", "lts": "2024-10-29", "maintenance": "2025-10-21", "end": "2027-04-30", diff --git a/data/ubuntu-distro-info.json b/data/ubuntu-distro-info.json index c718afdffe4b71..4337250c53a3fa 100644 --- a/data/ubuntu-distro-info.json +++ b/data/ubuntu-distro-info.json @@ -295,5 +295,12 @@ "eol": "2029-05-31", "eol_server": "2029-05-31", "eol_esm": "2034-04-25" + }, + "v24.10": { + "codename": "Oracular Oriole", + "series": "oracular", + "created": "2024-04-25", + "release": "2024-10-10", + "eol": "2025-07-10" } } diff --git a/docs/development/adding-a-package-manager.md b/docs/development/adding-a-package-manager.md index 715bb8b517e810..5b280da7303b8c 100644 --- a/docs/development/adding-a-package-manager.md +++ b/docs/development/adding-a-package-manager.md @@ -71,6 +71,8 @@ As another example, in order for Gradle to extract dependencies Renovate must fi The `extractAllPackageFiles` function takes an array of filenames as input. It returns an array of filenames and dependencies. +If you implement `extractAllPackageFiles` the manager must export as well either `updateDependency` or `extractPackageFile`. + ### `getRangeStrategy(config)` (optional) Write this optional function if you want the manager to support "auto" range strategies. diff --git a/docs/development/best-practices.md b/docs/development/best-practices.md index d2877339dbb299..efd62bf0018735 100644 --- a/docs/development/best-practices.md +++ b/docs/development/best-practices.md @@ -154,16 +154,14 @@ Avoid refactoring the code and tests at the same time, this can mask regression ## Logging -For `WARN`, `ERROR` and `FATAL log messages use logger metadata. +For `WARN`, `ERROR` and `FATAL` log messages use logger metadata. Also use logger metadata the result is a complex metadata object needing a multiple-line pretty stringification. -For `INFO` log messages inline the metadata into the log message. -Also, inline the metadata if the metadata object is complex. +For `INFO` and `DEBUG` log messages inline the metadata into the log message where feasible. +It is OK to not inline metadata if it's complex, but in that case first think whether that much information really needs to be logged. `WARN`, `ERROR` and `FATAL` messages are often used in metrics or error catching services. -These log messages should have a consistent `msg` component, so they can be automatically grouped or associated. -Metadata that is separate from its message is hard for humans to read. -Try to combine the metadata into the message, unless it is too complex to do so. +These log messages should have a static `msg` component, so they can be automatically grouped or associated. Good: diff --git a/docs/development/local-development.md b/docs/development/local-development.md index 0fe428f3d3e4e3..9bf2b48981f3ee 100644 --- a/docs/development/local-development.md +++ b/docs/development/local-development.md @@ -15,14 +15,14 @@ You need the following dependencies for local development: - pnpm `^8.6.11` (use corepack) - C++ compiler -We support Node.js versions according to the [Node.js release schedule](https://github.com/nodejs/Release#release-schedule). +We recommend you use the version of Node.js defined in the repository's `.nvmrc`. #### Linux You can use the following commands on Ubuntu. ```sh -curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash - +curl -sL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt-get update sudo apt-get install -y git build-essential nodejs corepack enable diff --git a/docs/development/minimal-reproductions.md b/docs/development/minimal-reproductions.md index ee17d2316650d4..e43e238cf9a6ed 100644 --- a/docs/development/minimal-reproductions.md +++ b/docs/development/minimal-reproductions.md @@ -2,7 +2,11 @@ We may ask you to create a "minimal reproduction" repository to help us fix bugs or work on a feature. -This document explains why we need a minimal reproduction, why we will not use your production repository to debug, and how to create a good minimal reproduction. +This document explains: + +- why we need a minimal reproduction +- why we will not use your production repository to debug +- how to create a good minimal reproduction ## Help yourself by creating a minimal reproduction @@ -16,37 +20,39 @@ It's fastest if you - as the bug reporter or feature requester - create the repr ## How the Renovate developers use your minimal reproduction -The first benefit of a public reproduction is to prove that the problem is not caused by your environment or by a setting you left out of your description, thinking it was not relevant. -If there were any doubts about whether you had found a genuine problem before, they are usually removed once a reproduction is made. +A reproduction confirms the problem is with Renovate, and _not_ with your environment, or your configuration settings. +A reproduction also helps us see where the bug or missing feature is, and to verify that the new code meets the requirements. -Next, when a reproduction has minimal config, it can often let us narrow down or even identify the root cause, suggest workarounds, etc. -This means we can often help you from code inspection alone. +When a reproduction has minimal config, we can oftern narrow down, or identify the root cause. +This helps us suggest workarounds. +Often we can help you from code inspection alone. -Finally, by making the code/dependencies minimal, it usually makes the problem feasible to step through using a debugging if code inspection was not sufficient. -Production repositories or non-minimal reproductions are often very difficult to debug because break points get triggered dozens or hundreds or times. +Finally, with minimal code and dependencies, we can step through with a debugger. +This helps when looking at the code is not enough to find the problem. +Production repositories, or non-minimal reproductions, are hard to debug as break points get triggered often. ## What is a minimal reproduction? -The basic idea of a minimal reproduction is to use the _least_ amount of both code and config to trigger missing or wrong behavior. -A minimal reproduction helps the developers see where the bug or missing feature is, and allows us to verify that the new code meets the requirements. +A minimal reproduction should have the _least_ amount of both code and config to trigger missing or wrong behavior. ## Where to host the minimal reproduction -If you can, use GitHub to host your reproduction. -If the reproduction needs to be on GitLab or Bitbucket, that is also okay. +Please put your reproduction in a public repository on GitHub.com, if possible. + +You may put the reproduction on another platform like GitLab or Bitbucket, _if_ the reproduction needs features/behavior of that platform. ## Creating a minimal reproduction There are two ways to create a minimal reproduction: -- Start with an empty repository and copy files from your production repository -- Start with a fork of your production repository and remove files and config +- Start with an empty repository and _copy_ files from your production repository +- Start with a fork of your production repository and _remove_ files and config General steps: -1. Create your minimal reproduction repository on GitHub, only use GitLab or Bitbucket if really needed +1. Put your minimal reproduction repository on GitHub, only use GitLab or Bitbucket if needed 1. Use the fewest number of repository files and dependencies -1. Reduce the Renovate config to a minimum +1. Reduce your Renovate config to a minimum 1. Remove private or secret information 1. Create a `readme.md` file that explains the current behavior and expected behavior 1. Set the repository visibility to `public` @@ -66,31 +72,27 @@ A production repository usually has: - many custom rules in the Renovate configuration file - many files that are not relevant -Having lots of "moving parts" makes debugging tricky, because debug break points can be triggered hundreds of times. +Having lots of "moving parts" makes it hard to debug, as debug break points can trigger hundreds of times. -When you have lots of custom config for Renovate, it's hard to find the root cause of the behavior. +When you have lots of custom config for Renovate, it's hard for us to find the root cause of the behavior. Bugs are often caused by multiple features interacting. -Reducing the config to a minimum helps us find out exactly which config elements are required to trigger the bug. +Reducing the config to a minimum helps us find exactly which config elements are needed to trigger the bug. ### "It's too much work to create a minimal reproduction" -We would love to get down to zero reported bugs or feature requests remaining, but we have a lot to do and must set our priorities. -This means we prefer working on issues with a minimal reproduction, as they allow us to spend our time efficiently. - -If you do not create a minimal reproduction, we will not prioritize working on your issue. - -Issues without a reproduction will probably stay open until you, or somebody else, creates a minimal reproduction. -After a while, issues without a reproduction may be closed unfixed. +If you do not create a minimal reproduction, the Renovate maintainers will not prioritize working on your issue. +Discussions without a reproduction will probably go stale unless you, or somebody else, creates a minimal reproduction. ### "I already described what you need in the issue" Thank you for describing your issue in detail. -But we still need a minimal reproduction in a repository, and we would like you to be the one to make sure it matches both your description and expected behavior. +But we still need a minimal reproduction in a repository. +We'd like you to make sure it matches both your description as well as expected behavior. ### Forcing Renovate to create a lot of pending updates Put an old version of a frequently updated dependency in your repository. -Set a high `minimumReleaseAge` for that dependency, for example: +Then set a high `minimumReleaseAge` for that dependency, for example: ```json { diff --git a/docs/usage/.pages b/docs/usage/.pages index b2e6c95ed28b32..809fc075758dc7 100644 --- a/docs/usage/.pages +++ b/docs/usage/.pages @@ -4,6 +4,7 @@ nav: - ... | getting-started - Troubleshooting: 'troubleshooting.md' - Configuration: + - 'Overview': 'config-overview.md' - 'Repository': 'configuration-options.md' - 'Self-hosted': 'self-hosted-configuration.md' - 'Presets': 'config-presets.md' @@ -31,6 +32,7 @@ nav: - 'Noise Reduction': 'noise-reduction.md' - 'Upgrade best practices': 'upgrade-best-practices.md' - Included Presets: + - 'Custom Manager Presets': 'presets-customManagers.md' - 'Default Presets': 'presets-default.md' - 'Docker Presets': 'presets-docker.md' - 'Full Config Presets': 'presets-config.md' @@ -40,7 +42,6 @@ nav: - 'npm Presets': 'presets-npm.md' - 'Package Presets': 'presets-packages.md' - 'Preview Presets': 'presets-preview.md' - - 'Regex Manager Presets': 'presets-regexManagers.md' - 'Replacement Presets': 'presets-replacements.md' - 'Schedule Presets': 'presets-schedule.md' - 'Security Presets': 'presets-security.md' @@ -49,11 +50,13 @@ nav: - ... | user-stories - 'Security and Permissions': 'security-and-permissions.md' - 'Merge Confidence': 'merge-confidence.md' + - 'Language constraints and upgrading': 'language-constraints-and-upgrading.md' - 'Templates': 'templates.md' - 'String Pattern Matching': 'string-pattern-matching.md' - 'Frequently Asked Questions': 'faq.md' - 'Known Limitations': 'known-limitations.md' - 'Release notes for major versions': 'release-notes-for-major-versions.md' - Bot comparison: 'bot-comparison.md' + - 'Logo and brand guidelines': 'logo-brand-guidelines.md' - About Us: 'about-us.md' - Contributing to Renovate: 'contributing-to-renovate.md' diff --git a/docs/usage/about-us.md b/docs/usage/about-us.md index 252c94308a58f9..7f8838c33521c3 100644 --- a/docs/usage/about-us.md +++ b/docs/usage/about-us.md @@ -42,6 +42,9 @@ Some features made a lot of people happy, and efficient! - [@cgrindel](https://github.com/cgrindel) created the `bazel-module` manager - [@RahulGautamSingh](https://github.com/RahulGautamSingh) refactored a lot of code and worked on performance improvements like reduced cloning during updates and onboarding - [@Gabriel-Ladzaretti](https://github.com/Gabriel-Ladzaretti) S3 repo cache, child process refactoring, others +- [@not7cd](https://github.com/not7cd) improved the `pip-compile` manager +- [@squidfunk](https://github.com/squidfunk) for creating and maintaining the [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) framework, that we use to build our docs, and for helping us with problems with the framework, or our docs +- [The MkDocs project](https://www.mkdocs.org/) for making the static site generator, that we use to build our docs ## Renovate development @@ -52,3 +55,5 @@ This is where we do most of the development. The Renovate docs are built from Markdown files in our [`renovatebot/renovate` repository](https://github.com/renovatebot/renovate). Most of the source files can be found in the [`docs/usage/` directory](https://github.com/renovatebot/renovate/tree/main/docs/usage). + +We use [MkDocs](https://www.mkdocs.org/) and [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) to build our docs. diff --git a/docs/usage/assets/images/matrix.png b/docs/usage/assets/images/matrix.png new file mode 100644 index 00000000000000..c305c6626d82d1 Binary files /dev/null and b/docs/usage/assets/images/matrix.png differ diff --git a/docs/usage/assets/images/swissquote_stats_collection.png b/docs/usage/assets/images/swissquote_stats_collection.png new file mode 100644 index 00000000000000..666ea1770487f9 Binary files /dev/null and b/docs/usage/assets/images/swissquote_stats_collection.png differ diff --git a/docs/usage/bot-comparison.md b/docs/usage/bot-comparison.md index 0e68188184f49b..d9ddc01fe66859 100644 --- a/docs/usage/bot-comparison.md +++ b/docs/usage/bot-comparison.md @@ -18,7 +18,7 @@ If you see anything wrong on this page, please let us know by creating a [Discus | Compatibility score badges | Four badges showing: Age, Adoption, Passing, Confidence | One badge with overall compatibility score | | Built-in to GitHub | No, requires app or self-hosting | Yes | | Scheduling | By default, Renovate runs as often as it is allowed to, read [Renovate scheduling](./key-concepts/scheduling.md) to learn more | Yes: `daily`, `weekly`, `monthly` | -| License | [GNU Affero General Public License](https://github.com/renovatebot/renovate/blob/main/license) | [The Prosperity Public License 2.0.0](https://github.com/dependabot/dependabot-core/blob/main/LICENSE) | +| License | [GNU Affero General Public License](https://github.com/renovatebot/renovate/blob/main/license) | [MIT License](https://github.com/dependabot/dependabot-core/blob/main/LICENSE) | | Programming language of project | TypeScript | Ruby | | Project pulse | [`renovatebot/renovate` monthly pulse](https://github.com/renovatebot/renovate/pulse/monthly) | [`dependabot-core` monthly pulse](https://github.com/dependabot/dependabot-core/pulse/monthly) | | Contributor graph | [`renovatebot/renovate` contributor graph](https://github.com/renovatebot/renovate/graphs/contributors) | [`dependabot-core` contributor graph](https://github.com/dependabot/dependabot-core/graphs/contributors) | @@ -114,7 +114,7 @@ Dependabot has four options that apply at a language level: Renovate uses the [GNU Affero General Public License](https://github.com/renovatebot/renovate/blob/main/license). -Dependabot uses [The Prosperity Public License 2.0.0](https://github.com/dependabot/dependabot-core/blob/main/LICENSE). +Dependabot uses the [MIT License](https://github.com/dependabot/dependabot-core/blob/main/LICENSE). Neither license is relevant to the end user though if consuming through an App/SaaS. @@ -148,6 +148,14 @@ Available [Renovate distributions](./getting-started/running.md#available-distri You can self-host Dependabot on other platforms than GitHub but none are officially supported. +#### As a GitHub Actions workflow on GitHub + +You can run Dependabot as a GitHub Actions workflow on hosted and self-hosted runners. +Learn more by reading the: + +- [GitHub Blog, Dependabot on GitHub Actions and self-hosted runners is now generally available](https://github.blog/2024-05-02-dependabot-on-github-actions-and-self-hosted-runners-is-now-generally-available/) +- [GitHub Docs, About Dependabot on GitHub Actions runners](https://docs.github.com/en/code-security/dependabot/working-with-dependabot/about-dependabot-on-github-actions-runners) + #### `dependabot-core` If you want to customize Dependabot, or self-host on another platform, you can use [`dependabot-core`](https://github.com/dependabot/dependabot-core). diff --git a/docs/usage/config-overview.md b/docs/usage/config-overview.md new file mode 100644 index 00000000000000..547baa7c485c42 --- /dev/null +++ b/docs/usage/config-overview.md @@ -0,0 +1,314 @@ +# Renovate configuration overview + +When Renovate runs on a repository, the final config used is derived from the: + +- Default config +- Global config +- Inherited config +- Repository config +- Resolved presets referenced in config + +## Types of config + +### Default config + +Every Renovate config option has a default value/setting. +That default value/setting may even be `null`. +You can find the default values on the Renovate docs website. + +For example: + +- The default value for `onboarding` is `true` +- The option `labels` lacks a default value, which means that no labels will be added to Renovate's PRs + +The default config is loaded first, and may be superseded/overridden by the configuration types listed below. + +### Global config + +Global config means: the config defined by the person or team responsible for running the bot. +This is also referred to as "bot config", because it's the config passed to the bot by the person running it. +Global config can contain config which is "global only" as well as any configuration options which are valid in Inherited config or Repository config. + +If you are an end user of Renovate, for example if you're using the Mend Renovate App, then you don't need to care as much about any global config. +As a end-user you can not change some settings because those settings are global-only. +If you are an end user, you can skip the rest of this "Global config" section and proceed to "Inherited config". + +Global config can be read from a file, environment variable, or CLI parameters. +You must configure at least one of these for Renovate to have the information it needs to run. +For example: you may need to give Renovate the correct credentials. + +#### File config + +Renovate first tries to read the global config from a file. +By default Renovate checks for a `config.js` file in the current working directory. +But you can override this by defining `RENOVATE_CONFIG_FILE` in env, for example: `RENOVATE_CONFIG_FILE=/tmp/my-renovate-config.js`. + +By default Renovate allows the config file to be _missing_ and does not error if it cannot find it. +But if you have configured `RENOVATE_CONFIG_FILE` and the path you specified is not found then Renovate will error and exit, because it assumes you have a configuration problem. +If the file is found but cannot be parsed then Renovate will also error and exit. + +Global config files can be `.js` or `.json` files. +You may use synchronous or asynchronous methods inside a `.js` file, including even to fetch config information from remote hosts. + +#### Environment config + +Global config can be defined using environment variables. +The config options that you can use in environment variables all have the prefix `RENOVATE_`. +For example, `RENOVATE_PLATFORM=gitlab` is the same as setting `"platform": "gitlab"` in File config. + +Usually there's a clear mapping from configuration option name to the corresponding Environment config name. +But we recommend you still check the documentation for the field `env` for each option to make sure. +If the configuration option lacks a `env` field, the config option also lacks a Environment config variable name. + +A special case for Environment config is the `RENOVATE_CONFIG` "meta" config option. +The `RENOVATE_CONFIG` option accepts a stringified full config, for example: `RENOVATE_CONFIG={"platform":"gitlab","onboarding":false}`. +Any additional Environment config variables take precedence over values in `RENOVATE_CONFIG`. + +##### Environment variable examples + + +!!! warning + Make sure to escape any punctuation. + Be extra careful if you're passing stringified values. + +Boolean: + +- `RENOVATE_ONBOARDING=true` + +String: + +- `RENOVATE_BASE_DIR=/tmp/something` +- `RENOVATE_BASE_DIR="/tmp/some thing"` + +Number: + +- `RENOVATE_PR_HOURLY_LIMIT=1` + +List with numbers or strings: + +- `RENOVATE_LABELS="abc,def,label with space"` + +Objects, or lists with objects: + +- `RENOVATE_CONFIG="{platform\":\"gitlab\",\"onboarding\":false}"` +- `RENOVATE_PACKAGE_RULES="[{matchHost:\"gitlab\",token:\"$SOME_TOKEN\"}]"` + + +!!! tip + Use "stringify" ([Example online service](https://jsonformatter.org/json-stringify-online)) for strings and objects. + +##### Experimental variables + +Renovate has "experimental" environment variables, which start with `RENOVATE_X_`. +These variables are experimental, can be changed at any time, and are not parsed as part of regular configuration. +Read the [Self-hosted experimental environment variables](./self-hosted-experimental.md) docs to learn more. + +##### Logging variables + +Finally, there are some special environment variables that are loaded _before_ configuration parsing because they are used during logging initialization: + +- `LOG_CONTEXT`: a unique identifier used in each log message to track context +- `LOG_FORMAT`: defaults to a "pretty" human-readable output, but can be changed to "json" +- `LOG_LEVEL`: most commonly used to change from the default `info` to `debug` logging + +#### CLI config + +The final way to configure Global config is through CLI parameters. +For example, the CLI parameter `--platform=gitlab` is the same as setting `"platform": "gitlab"` in File config or `RENOVATE_PLATFORM=gitlab` in Environment config. + +CLI config is read last and takes precedence over Environment and File config. +For example, if you configure conflicting values in Environment, File config and CLI config, then the CLI config will be merged last and "win" if values conflict. + +It is important that you: + +- Always provide a value, even if the field is boolean (e.g. `--onboarding=true` and _not_ `--onboarding`), and +- Prefer `=` notation over spaces, e.g. `--onboarding=true` instead of `--onboarding true` + +### Inherited config + +#### Use cases + +The primary purpose of Inherited config is to allow for default settings of an organization/group. +Two main use cases for Inherited config are: + +- Controlling onboarding settings within an org (e.g. disabling onboarding, making config optional) +- Defining default config settings for repos within an org + +We recommend that organizations use shared presets instead of Inherited config, if possible. +But default settings through Inherited config are useful if: + +- You want to avoid setting Repository config in each repo, or +- You onboarded many repos prior to having a shared org config, and don't want to retrospectively edit each repo's config + +#### How it's found + +If `inheritConfig` is `true` in Global config then Renovate will look for Inherited config before processing each repository. +The repository and file name which Renovate looks for can be configured using the other `inheritConfig*` settings documented in Global config. +Default values are `{{parentOrg}}/renovate-config` for repository name and `org-inherited-config.json` for file name. + +If found, Inherited config will be merged on top (i.e. override) Global config. +Avoid putting any global-only setting in a Inherited config, as doing so will result in an error. + +Inherited config may use all Repository config settings, and any Global config options which have the "supportsInheritConfig" property in the docs. + +For information on how the Mend Renovate App supports Inherited config, see the dedicated "Mend Renovate App Config" section toward the end of this page. + +### Repository config + +Repository config is the config loaded from a config file in the repository. +Alternative file names are supported, but the default is `renovate.json`. +If Renovate finds more than one configuration file in the same repository, then Renovate will use the _first_ configuration file it finds and ignores the other(s). + +### Config precedence + +Once Repository config is loaded, it is merged over the top of the previously loaded Global and Inherited config, meaning it takes precedence over them. +Presets referenced with an "extends" config are resolved first and take lower precedence over regular/raw config in the same file or config object. + +## Onboarding + +When Renovate processes a repository, one of the first decisions it makes is "Does this repository need to be onboarded?". +By default, Renovate will create an "Onboarding PR" with a default config if a repository does not have a Repository config file committed to the default branch. + +### Onboarding Config + +When Renovate creates an Onboarding PR it will propose a Repository config file to be merged. +By default, it is essentially an empty config with only the Renovate JSON schema referenced, but you can change this behavior if desired. + +If you configure `onboardingConfig` in either Global config or Inherited config then Renovate will use that config directly instead of the default. + +Alternatively if you follow Renovate's naming convention for shared presets then it can automatically detect those instead. +If the repository `{{parentOrg}}/renovate-config` has a `default.json` file then this will be treated as the organization's default preset and included in the Onboarding config. +Additionally for platforms which support nested Organization/Group hierarchies, Renovate will "hunt" up such hierarchies for a `renovate-config` repository with default config and stop when it finds the first. + + +!!! note + Renovate will also check for a `renovate.json` file if it cannot find a `default.json` file in a preset, however this option is deprecated and not recommended. + +If a default config is not found in a `renovate-config` repository within the Organization, Renovate will also check for the presence of a `renovate-config.json` file within a `.{{platform}}` repository parallel to the current repository. +For example if the repository being onboarded is `abc/def` on a GitHub platform then Renovate will look for the existence of an `abc/.github` repository containing a `renovate-config.json` file. + +### Changing default behavior + +Default onboarding behavior for an Organization can be changed either in Global or Inherited config. + +For example, if you set `onboarding=false` then Renovate will not onboard repositories, and skip any repositories without a Repository config. +In other words, users need to manually push a valid Repository config file to activate Renovate on the repository. + +If you set `onboarding=false` plus `requireConfig=optional` then it means Renovate will skip onboarding and proceed to run on a repository, even if Renovate does not find any Repository config. + +## Shared Presets + +### Overview + +The concept of shared configuration is covered in detail on the [Presets](./key-concepts/presets.md) page, so please read that first. + +### Use of Presets in Global config + +Presets should be used cautiously in Global config as they often lead to misunderstandings. + +#### globalExtends + +Sometimes you may not wish to put all settings within the Global config itself and instead commit it to a repository which is then referenced from the Global config. +In such cases, use `globalExtends` instead of `extends` so that it is resolved immediately and used as part of Global config. + +#### extends + +If you use `extends` within Global config then it's important to note that these are _not_ resolved/expanded during Global config processing and instead are passed through unresolved to be part of Repository config. +Passing `extends` through to be part of Repository config has two major consequences: + +- It allows repository users to be able to use `ignorePresets` to ignore all or part of the `extends` presets, and +- Presets defined within `extends` in Global config will take _higher_ precedence that "regular" Global config, because it's resolved later + +### Using a centralized config + +Using "centralized" configs through Renovate presets is important in order to be able to: + +- Save time by not repeating yourself in every repo with the same config, and +- Being able to change settings across an entire Organization or groups of repositories in one place + +Once you've created a centralized preset config, there are multiple ways you can pass it through to repositories: + +- Defining it in Global config (either `globalExtends` or `extends`) +- Using it as your Inherited config, or referencing it from Inherited config using `extends` +- Ensuring it's referenced in Onboarding config so that it's committed as part of the Repository config + +The above possibilities go from least to most transparent when it comes to end users. + +Global config may be invisible to developers without log access, meaning they could be confused by any settings you apply - via presets or directly - within Global config. +For example the developers wonder why Renovate is behaving differently to its documented default behavior and may even think it's a bug. + +Inherited config is visible to developers (it's within a repository they can see) although it's _implicitly_ applied so without log access and if they're not aware to look for an Inherited config repository then they may again be a little confused as to why default behavior has changed. + +The recommended approach for using a centralized preset is to explicitly "extend" it from every repository, which can be achieved easily if it's part of your `onboardingConfig`. +By having your centralized preset part of each Repository config `extends`, it has these two benefits: + +- You still have the ability to change shared settings in a single location +- Any user viewing the repo can see the preset being extended and trace it back to understand which config is applied + +## Mend Renovate App Config + +The [Mend Renovate App](https://github.com/apps/renovate) is a popular way to use Renovate on GitHub.com so it's important that any of its non-default behavior is documented here. + +Importantly, logs for all Renovate jobs by the Mend Renovate App are available through the [Mend Developer Portal](https://developer.mend.io) so end users can view the logs to see which settings are applied. + +### Onboarding behavior + +#### Installing Renovate into all repositories leads to silent mode + +If an Organization installed Renovate with "All repositories" (instead of "Selected repositories"), then Renovate will default to "Silent" mode (`dryRun=lookup`). +We chose this behavior because: + +- Too often an account or org administrator selects the "All repositories" option and accidentally onboards hundreds of repositories, and +- By offering this option, it means that org administrators _can_ install Renovate into "All repositories" without worrying about the noise, and let individual repository admins decide if/when to start onboarding + +##### Why we call this silent mode + +- It's not just no PRs, it's also no Issues +- It's a common term across other Mend capabilities, such as OSS security and SAST security, where status checks also use silent/non-silent + +#### Get onboarding PRs from Renovate by getting out of silent mode + +If Renovate is installed, _and_ you can see a job log, but Renovate is _not_ onboarding your repository: look for `dryRun` in the logs to confirm you are in Silent mode. +To get a onboarding PR from Renovate, change to Interactive mode either at the Repository level or Organization level. + +#### Installing Renovate into selected repositories always leads to onboarding PRs + +Additionally, if an Organization is installed with "Selected repositories" then the app will change `onboardingNoDeps` to `true` so that an Onboarding PR is created even if no dependencies are detected. + +### Fork Processing + +If an Organization install Renovate with the "All repositories" option, then `forkProcessing` will remain as the default value `false`. +This means forked repositories are _not_ onboarded, Renovate essentially ignores them. +To change this behavior you need to manually push a `renovate.json` to the repository with `"forkProcessing": true`. + +If an Organization installs Renovate with "Selected repositories" then we assume the organization wants all of the selected repositories onboarded (even forked repositories), so `forkProcessing` is set to `true`. + +### Default presets + +The Mend Renovate app automatically adds the `mergeConfidence:all-badges` preset to the `extends` array. +If you don't want the Merge Confidence badges, then add the `mergeConfidence:all-badges` preset to the `ignorePresets` array. + +Additionally, the preset `config:recommended` is added to `onboardingConfig`. + +### Allowed Post-upgrade commands + +A limited set of approved `postUpgradeTasks` commands are allowed in the app. +They are not documented here as they may change over time - please consult the logs to see them. + +## Other + +The below contains edge cases which you should avoid if possible, and likely don't need to use. +They are included here because they can cause "exceptions" to some of the previously mentioned rules of config. + +### Optimize for Disabled + +The `optimizeForDisabled` option was designed for an edge case where a large percentage of repos are disabled by config. +If this option is set to `true`, Renovate will use a platform API call to see if a `renovate.json` exists and if it contains `"enabled": false`. +If so, the repository will be skipped without a clone necessary. +If the file is not present or does not disable Renovate, then Renovate continues as before (having "wasted" that extra API call). + +### Force config + +We recommend you avoid the `force` config option, if possible. + +It can be used to "force" config over the top of other config or rules which might be merged later, so at times can cause confusion - especially if it's defined in Global config and overriding settings in Repository config. diff --git a/docs/usage/config-presets.md b/docs/usage/config-presets.md index 84135298e0bc6d..a29253b9438258 100644 --- a/docs/usage/config-presets.md +++ b/docs/usage/config-presets.md @@ -10,15 +10,6 @@ Read the [Key concepts, presets](./key-concepts/presets.md) page to learn more a Shareable config presets must use the JSON or JSON5 formats, other formats are not supported. - -!!! warning - Only use `default.json` for your presets. - - -!!! warning - We have deprecated using a `renovate.json` file for presets, as this causes issues if the repository configuration _also_ uses a `renovate.json` file. - If you are using a `renovate.json` file to share your presets, rename it to `default.json`. - !!! tip Describe what your preset does in the `"description"` field. @@ -42,6 +33,17 @@ Alternatively, Renovate can fetch preset files from an HTTP server. You can set a Git tag (like a SemVer) to use a specific release of your shared config. +### Preset File Naming + +Presets are repo-hosted, and you can have one or more presets hosted per repository. +If you omit a file name from your preset (e.g. `github>abc/foo`) then Renovate will look for a `default.json` file in the repo. +If you wish to have an alternative file name, you need to specify it (e.g. `github>abc/foo//alternative-name.json5`). + + +!!! warning + We've deprecated using a `renovate.json` file for the default _preset_ file name in a repository. + If you're using a `renovate.json` file to share your presets, rename it to `default.json`. + ### GitHub | name | example use | preset | resolves as | filename | Git tag | @@ -165,7 +167,7 @@ Here is how you would use these in your Renovate config: In short, the number of `{{argx}}` parameters in the definition is how many parameters you need to provide. Parameters must be strings, non-quoted, and separated by commas if there are more than one. -If you find that you are repeating config a lot, you might consider publishing one of these types of parameterised presets yourself. +If you find that you are repeating config a lot, you might consider publishing one of these types of parameterized presets yourself. Or if you think your preset would be valuable for others, please contribute a PR to the Renovate repository, see [Contributing to presets](#contributing-to-presets). ## GitHub-hosted Presets @@ -232,6 +234,40 @@ Parameters are supported similar to other methods: } ``` +## Templating presets + +You can use [Handlebars](https://handlebarsjs.com/) templates to be flexible with your presets. +This can be handy when you want to include presets conditionally. + + +!!! note + The template only supports a small subset of options, but you can extend them via `customEnvVariables`. + +Read the [templates](./templates.md) section to learn more. + +### Example use-case + +The following example shows a self-hosted Renovate preset located in a GitLab repository called `renovate/presets`. + +```json +{ + "extends": ["local>renovate/presets"] +} +``` + +Usually you want to validate the preset before you put it in your Renovate configuration +Here is an example of how you can use templating to validate and load the preset on a branch level: + +```javascript +// config.js +module.exports = { + customEnvVariables: { + GITLAB_REF: process.env.CI_COMMIT_REF_NAME || 'main', + }, + extends: ['local>renovate/presets#{{ env.GITLAB_REF }}'], +}; +``` + ## Contributing to presets Have you configured a rule that could help others? diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index 7dc419a3b9185a..063df084f9de9e 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -368,11 +368,6 @@ Solutions: ## branchName - -!!! warning - We strongly recommended that you avoid configuring this field directly. - Use at your own risk. - If you truly need to configure this then it probably means either: - You are hopefully mistaken, and there's a better approach you should use, so open a new "config help" discussion at the [Renovate discussions tab](https://github.com/renovatebot/renovate/discussions) or @@ -459,7 +454,7 @@ For example, To add `[skip ci]` to every commit you could configure: } ``` -Another example would be if you want to configure a DCO sign-off to each commit. +Another example would be if you want to configure a DCO sign off to each commit. If you want Renovate to sign off its commits, add the [`:gitSignOff` preset](./presets-default.md#gitsignoff) to your `extends` array: @@ -473,11 +468,6 @@ If you want Renovate to sign off its commits, add the [`:gitSignOff` preset](./p ## commitMessage - -!!! warning - We deprecated editing the `commitMessage` directly, and we recommend you stop using this config option. - Instead, use config options like `commitMessageAction`, `commitMessageExtra`, and so on, to create the commit message you want. - ## commitMessageAction This is used to alter `commitMessage` and `prTitle` without needing to copy/paste the whole string. @@ -531,12 +521,12 @@ Composer `2.2` and up will be run with `--ignore-platform-req='ext-*' --ignore-p Older Composer versions will be run with `--ignore-platform-reqs`, which means that all platform constraints (including the PHP version) will be ignored by default. This can result in updated dependencies that are not compatible with your platform. -To customize this behaviour, you can explicitly ignore platform requirements (for example `ext-zip`) by setting them separately in this array. +To customize this behavior, you can explicitly ignore platform requirements (for example `ext-zip`) by setting them separately in this array. Each item will be added to the Composer command with `--ignore-platform-req`, resulting in it being ignored during its invocation. Note that this requires your project to use Composer V2, as V1 does not support excluding single platform requirements. The used PHP version will be guessed automatically from your `composer.json` definition, so `php` should not be added as explicit dependency. -If an empty array is configured, Renovate uses its default behaviour. +If an empty array is configured, Renovate uses its default behavior. Set to `null` (not recommended) to fully omit `--ignore-platform-reqs/--ignore-platform-req` during Composer invocation. This requires the Renovate image to be fully compatible with your Composer platform requirements in order for the Composer invocation to succeed, otherwise Renovate will fail to create the updated lock file. @@ -638,12 +628,18 @@ Renovate supports two options: More advanced filtering options may come in the future. There must be a `constraints` object in your Renovate config, or constraints detected from package files, for this to work. +Additionally, the "datasource" within Renovate must be capable of returning `constraints` values about each package's release. + This feature is limited to the following datasources: +- `crate` - `jenkins-plugins` - `npm` - `packagist` - `pypi` +- `rubygems` + +Sometimes when using private registries they may omit constraints information, which then is another reason such filtering may not work even if the datasource and corresponding default public registry supports it. !!! warning @@ -693,7 +689,7 @@ The `regex` manager which is based on using Regular Expression named capture gro You must have a named capture group matching (e.g. `(?.*)`) _or_ configure its corresponding template (e.g. `depNameTemplate`) for these fields: - `datasource` -- `depName` +- `depName` and / or `packageName` - `currentValue` Use named capture group matching _or_ set a corresponding template. @@ -707,7 +703,7 @@ For template fields, use the triple brace `{{{ }}}` notation to avoid Handlebars !!! tip - Look at our [Regex Manager Presets](./presets-regexManagers.md), they may have what you need. + Look at our [Custom Manager Presets](./presets-customManagers.md), they may have what you need. ### customType @@ -774,9 +770,9 @@ As example the following configuration will update all three lines in the Docker ``` ```dockerfile title="Dockerfile" -FROM amd64/ubuntu:18.04 -ENV GRADLE_VERSION=6.2 # gradle-version/gradle&versioning=maven -ENV NODE_VERSION=10.19.0 # github-tags/nodejs/node&versioning=node +FROM amd64/ubuntu:24.04 +ENV GRADLE_VERSION=6.2 # gradle-version/gradle&versioning=maven +ENV NODE_VERSION=10.19.0 # github-tags/nodejs/node&versioning=node ``` #### recursive @@ -1255,7 +1251,7 @@ It is valid only as a top-level configuration option and not, for example, withi !!! warning - The bot administrator must configure a list of allowed environment names in the [`allowedEnv`](./self-hosted-configuration.md#allowedEnv) config option, before users can use those allowed names in the `env` option. + The bot administrator must configure a list of allowed environment names in the [`allowedEnv`](./self-hosted-configuration.md#allowedenv) config option, before users can use those allowed names in the `env` option. Behavior: @@ -1406,7 +1402,7 @@ For now, you can only use this option on the GitLab platform. For `followTag` to work, the datasource must support distribution streams or tags, like for example npm does. -The main usecase is to follow a pre-release tag of a dependency, say TypeScript's `"insiders"` build: +The main use case is to follow a pre-release tag of a dependency, say TypeScript's `"insiders"` build: ```json { @@ -1446,7 +1442,7 @@ If this option is enabled, reviewers will need to create a new PR if more change By default, Renovate skips any forked repositories when in `autodiscover` mode. It even skips a forked repository that has a Renovate configuration file, because Renovate does not know if that file was added by the forked repository. -**Process a fork in `autodiscover` mode`** +**Process a fork in `autodiscover` mode** If you want Renovate to run on a forked repository when in `autodiscover` mode then: @@ -1852,7 +1848,7 @@ Enable got [http2](https://github.com/sindresorhus/got/blob/v11.5.2/readme.md#ht You can provide a `headers` object that includes fields to be forwarded to the HTTP request headers. By default, all headers starting with "X-" are allowed. -A bot administrator may configure an override for [`allowedHeaders`](./self-hosted-configuration.md#allowedHeaders) to configure more permitted headers. +A bot administrator may configure an override for [`allowedHeaders`](./self-hosted-configuration.md#allowedheaders) to configure more permitted headers. `headers` value(s) configured in the bot admin `hostRules` (for example in a `config.js` file) are _not_ validated, so it may contain any header regardless of `allowedHeaders`. @@ -1954,6 +1950,27 @@ registry=https://gitlab.myorg.com/api/v4/packages/npm/ !!! note Values containing a URL path but missing a scheme will be prepended with 'https://' (e.g. `domain.com/path` -> `https://domain.com/path`) +### readOnly + +If the `readOnly` field is being set to `true` inside the host rule, it will match only against the requests that are known to be read operations. +Examples are `GET` requests or `HEAD` requests, but also it could be certain types of GraphQL queries. + +This option could be used to avoid rate limits for certain platforms like GitHub or Bitbucket, by offloading the read operations to a different user. + +```json +{ + "hostRules": [ + { + "matchHost": "api.github.com", + "readOnly": true, + "token": "********" + } + ] +} +``` + +If more than one token matches for a read-only request then the `readOnly` token will be given preference. + ### timeout Use this figure to adjust the timeout for queries. @@ -2042,10 +2059,13 @@ Applicable for Composer only for now. ## ignorePrAuthor -This is usually needed if someone needs to migrate bot accounts, including from the Mend Renovate App to the self-hosted variant. +This is usually needed if someone needs to migrate bot accounts, including from the Mend Renovate App to self-hosted. +An additional use case is for GitLab users of project or group access tokens who need to rotate them. + If `ignorePrAuthor` is configured to true, it means Renovate will fetch the entire list of repository PRs instead of optimizing to fetch only those PRs which it created itself. You should only want to enable this if you are changing the bot account (e.g. from `@old-bot` to `@new-bot`) and want `@new-bot` to find and update any existing PRs created by `@old-bot`. -It's recommended to revert this setting once that transition period is over and all old PRs are resolved. + +Setting this field to `true` in GitLab will also mean that all Issues will be fetched instead of only those by the bot itself. ## ignorePresets @@ -2055,11 +2075,11 @@ For example, consider this config: ```json { "extends": ["config:recommended"], - "ignorePresets": [":prHourlyLimit2"] + "ignorePresets": ["group:monorepos"] } ``` -It would take the entire `"config:recommended"` preset - which has a lot of sub-presets - but ignore the `":prHourlyLimit2"` rule. +It would take the entire `"config:recommended"` preset - which has a lot of sub-presets - but ignore the `"group:monorepos"` rule. ## ignoreReviewers @@ -2123,6 +2143,19 @@ Currently, this applies to the `minimumReleaseAge` check only. The `flexible` mode can result in "flapping" of Pull Requests, for example: a pending PR with version `1.0.3` is first released but then downgraded to `1.0.2` once it passes `minimumReleaseAge`. We recommend that you use the `strict` mode, and enable the `dependencyDashboard` so that you can see suppressed PRs. +## keepUpdatedLabel + +On supported platforms you may add a label to a PR so that Renovate recreates/rebases the PR when the branch falls behind the base branch. +Adding the `keepUpdatedLabel` label to a PR makes Renovate behave as if `rebaseWhen` were set to `behind-base-branch`, but only for the given PR. +Renovate does _not_ remove the label from the PR after it finishes rebasing. +This is different from the `rebaseLabel` option, where Renovate _removes_ the label from the PR after rebasing. + +`keepUpdatedLabel` can be useful when you have approved certain PRs and want Renovate to keep the PRs up-to-date until you're ready to merge them. +The setting `keepUpdatedLabel` is best used in this scenario: + +- By default, you configure `rebaseWhen` to `never` or `conflicted` to reduce rebasing +- Sometimes, you want Renovate to keep specific PRs up-to-date with their base branch (equivalent to `rebaseWhen=behind-base-branch`) + ## labels By default, Renovate does not add any labels to PRs. @@ -2146,10 +2179,10 @@ Consider this example: With the above config, every PR raised by Renovate will have the label `dependencies` while PRs containing `eslint`-related packages will instead have the label `linting`. -Renovate only adds labels when it creates the PR, which means: +Behavior details: -- If you remove labels which Renovate added, it does not re-apply them -- If you change your config, the new/changed labels are not applied to any open PRs +- On GitHub, GitLab and Gitea: Renovate will keep PR labels in sync with configured labels, provided that no other user or bot has made changes to the labels after PR creation. If labels are changed by any other account, Renovate will stop making further changes. +- For other platforms, Renovate will add labels only at time of PR creation and not update them after that. The `labels` array is non-mergeable, meaning if multiple `packageRules` match then Renovate uses the last value for `labels`. If you want to add/combine labels, use the `addLabels` config option, which is mergeable. @@ -2293,6 +2326,24 @@ This works because Renovate will add a "renovate/stability-days" pending status Add to this object if you wish to define rules that apply only to minor updates. +## mode + +This configuration option was created primarily for use with Mend's hosted app, but can also be useful for some self-hosted use cases. + +It enables a new `silent` mode to allow repos to be scanned for updates _and_ for users to be able to request such updates be opened in PRs _on demand_ through the Mend UI, without needing the Dependency Dashboard issue in the repo. + +Although similar, the options `mode=silent` and `dryRun` can be used together. +When both are configured, `dryRun` takes precedence, so for example PRs won't be created. + +Configuring `silent` mode is quite similar to `dryRun=lookup` except: + +- It will bypass onboarding checks (unlike when performing a dry run on a non-onboarded repo) similar to `requireConfig=optional` +- It can create branches/PRs if `checkedBranches` is set +- It will keep any existing branches up-to-date (e.g. ones created previously using `checkedBranches`) + +When in `silent` mode, Renovate does not create issues (such as Dependency Dashboard, or due to config errors) or Config Migration PRs, even if enabled. +It also does not prune/close any which already exist. + ## npmToken See [Private npm module support](./getting-started/private-packages.md) for details on how this is used. @@ -2422,8 +2473,9 @@ For example, you have multiple `package.json` and want to use `dependencyDashboa ### allowedVersions -Use this - usually within a packageRule - to limit how far to upgrade a dependency. -For example, if you wish to upgrade to Angular v1.5 but not to `angular` v1.6 or higher, you could define this to be `<= 1.5` or `< 1.6.0`: +You can use `allowedVersions` - usually within a `packageRules` entry - to limit how far to upgrade a dependency. + +For example, if you want to upgrade to Angular v1.5 but _not_ to `angular` v1.6 or higher, you could set `allowedVersions` to `<= 1.5` or `< 1.6.0`: ```json { @@ -2436,10 +2488,14 @@ For example, if you wish to upgrade to Angular v1.5 but not to `angular` v1.6 or } ``` -The valid syntax for this will be calculated at runtime because it depends on the versioning scheme, which is itself dynamic. +Renovate calculates the valid syntax for this at runtime, because it depends on the dynamic versioning scheme. + +#### Using regular expressions + +You can use Regular Expressions in the `allowedVersion` config. +You must _begin_ and _end_ your Regular Expression with the `/` character! -This field also supports Regular Expressions if they begin and end with `/`. -For example, the following will enforce that only 3 or 4-part versions are supported, without any prefixes: +For example, this config only allows 3 or 4-part versions, without any prefixes in the version: ```json { @@ -2452,8 +2508,12 @@ For example, the following will enforce that only 3 or 4-part versions are suppo } ``` -This field also supports a special negated regex syntax for ignoring certain versions. -Use the syntax `!/ /` like the following: +Again: note how the Regular Expression _begins_ and _ends_ with the `/` character. + +#### Ignore versions with negated regex syntax + +You can use a special negated regex syntax to ignore certain versions. +You must use the `!/ /` syntax, like this: ```json { @@ -2501,6 +2561,8 @@ Invalid if used outside a `packageRule`. ### excludeDepPatterns +### excludeDepPrefixes + ### excludePackageNames **Important**: Do not mix this up with the option `ignoreDeps`. @@ -2687,8 +2749,21 @@ Use this field to restrict rules to a particular datasource. e.g. This option is matched against the `currentValue` field of a dependency. -`matchCurrentValue` supports Regular Expressions which must begin and end with `/`. -For example, the following enforces that only `1.*` versions will be used: +`matchCurrentValue` supports Regular Expressions and glob patterns. For example, the following enforces that updates from `1.*` versions will be merged automatically: + +```json +{ + "packageRules": [ + { + "matchPackagePatterns": ["io.github.resilience4j"], + "matchCurrentValue": "1.*", + "automerge": true + } + ] +} +``` + +Regular Expressions must begin and end with `/`. ```json { @@ -2738,7 +2813,7 @@ Consider using instead `matchCurrentValue` if you wish to match against the raw } ``` -The syntax of the version range must follow the [versioning scheme](modules/versioning.md#supported-versioning) used by the matched package(s). +The syntax of the version range must follow the [versioning scheme](modules/versioning/index.md#supported-versioning) used by the matched package(s). This is usually defined by the [manager](modules/manager/index.md#supported-managers) which discovered them or by the default versioning for the package's [datasource](modules/datasource/index.md). For example, a Gradle package would typically need Gradle constraint syntax (e.g. `[,7.0)`) and not SemVer syntax (e.g. `<7.0`). @@ -2819,14 +2894,31 @@ It is recommended that you avoid using "negative" globs, like `**/!(package.json ### matchDepNames +This field behaves the same as `matchPackageNames` except it matches against `depName` instead of `packageName`. + ### matchDepPatterns +### matchDepPrefixes + ### matchNewValue This option is matched against the `newValue` field of a dependency. -`matchNewValue` supports Regular Expressions which must begin and end with `/`. -For example, the following enforces that only `1.*` versions will be used: +`matchNewValue` supports Regular Expressions and glob patterns. For example, the following enforces that updates to `1.*` versions will be merged automatically: + +```json +{ + "packageRules": [ + { + "matchPackagePatterns": ["io.github.resilience4j"], + "matchNewValue": "1.*", + "automerge": true + } + ] +} +``` + +Regular Expressions must begin and end with `/`. ```json { @@ -2873,6 +2965,12 @@ See also `excludePackageNames`. The above will configure `rangeStrategy` to `pin` only for the package `angular`. + +!!! note + `matchPackageNames` will try matching `packageName` first and then fall back to matching `depName`. + If the fallback is used, Renovate will log a warning, because the fallback will be removed in a future release. + Use `matchDepNames` instead. + ### matchPackagePatterns Use this field if you want to have one or more package names patterns in your package rule. @@ -2891,6 +2989,12 @@ See also `excludePackagePatterns`. The above will configure `rangeStrategy` to `replace` for any package starting with `angular`. + +!!! note + `matchPackagePatterns` will try matching `packageName` first and then fall back to matching `depName`. + If the fallback is used, Renovate will log a warning, because the fallback will be removed in a future release. + Use `matchDepPatterns` instead. + ### matchPackagePrefixes Use this field to match a package prefix without needing to write a regex expression. @@ -2909,8 +3013,11 @@ See also `excludePackagePrefixes`. Like the earlier `matchPackagePatterns` example, the above will configure `rangeStrategy` to `replace` for any package starting with `angular`. -`matchPackagePrefixes` will match against `packageName` first, and then `depName`, however `depName` matching is deprecated and will be removed in a future major release. -If matching against `depName`, use `matchDepPatterns` instead. + +!!! note + `matchPackagePrefixes` will try matching `packageName` first and then fall back to matching `depName`. + If the fallback is used, Renovate will log a warning, because the fallback will be removed in a future release. + Use `matchDepPatterns` instead. ### matchSourceUrlPrefixes @@ -3108,6 +3215,39 @@ For example to replace the npm package `jade` with version `2.0.0` of the packag } ``` +### prPriority + +Sometimes Renovate needs to rate limit its creation of PRs, e.g. hourly or concurrent PR limits. +By default, Renovate sorts/prioritizes based on the update type, going from smallest update to biggest update. +Renovate creates update PRs in this order: + +1. `pinDigest` +1. `pin` +1. `digest` +1. `patch` +1. `minor` +1. `major` + +If you have dependencies that are more or less important than others then you can use the `prPriority` field for PR sorting. +The default value is 0, so setting a negative value will make dependencies sort last, while higher values sort first. + +Here's an example of how you would define PR priority so that `devDependencies` are raised last and `react` is raised first: + +```json +{ + "packageRules": [ + { + "matchDepTypes": ["devDependencies"], + "prPriority": -1 + }, + { + "matchPackageNames": ["react"], + "prPriority": 5 + } + ] +} +``` + ## patch Add to this object if you wish to define rules that apply only to patch updates. @@ -3137,8 +3277,7 @@ If enabled Renovate will pin Docker images or GitHub Actions by means of their S If you have enabled `automerge` and set `automergeType=pr` in the Renovate config, then leaving `platformAutomerge` as `true` speeds up merging via the platform's native automerge functionality. -Renovate tries platform-native automerge only when it initially creates the PR. -Any PR that is being updated will be automerged with the Renovate-based automerge. +On GitHub and GitLab, Renovate re-enables the PR for platform-native automerge whenever it's rebased. `platformAutomerge` will configure PRs to be merged after all (if any) branch policies have been met. This option is available for Azure, Gitea, GitHub and GitLab. @@ -3393,39 +3532,6 @@ This is why we configured an upper limit for how long we wait until creating a P !!! note If the option `minimumReleaseAge` is non-zero then Renovate disables the `prNotPendingHours` functionality. -## prPriority - -Sometimes Renovate needs to rate limit its creation of PRs, e.g. hourly or concurrent PR limits. -By default, Renovate sorts/prioritizes based on the update type, going from the smallest update to the biggest update. -Renovate creates update PRs in this order: - -1. `pinDigest` -1. `pin` -1. `digest` -1. `patch` -1. `minor` -1. `major` - -If you have dependencies that are more or less important than others then you can use the `prPriority` field for PR sorting. -The default value is 0, so setting a negative value will make dependencies sort last, while higher values sort first. - -Here's an example of how you would define PR priority so that `devDependencies` are raised last and `react` is raised first: - -```json -{ - "packageRules": [ - { - "matchDepTypes": ["devDependencies"], - "prPriority": -1 - }, - { - "matchPackageNames": ["react"], - "prPriority": 5 - } - ] -} -``` - ## prTitle The PR title is important for some of Renovate's matching algorithms (e.g. determining whether to recreate a PR or not) so ideally do not modify it much. @@ -3541,8 +3647,10 @@ This feature works with the following managers: - [`docker-compose`](modules/manager/docker-compose/index.md) - [`dockerfile`](modules/manager/dockerfile/index.md) - [`droneci`](modules/manager/droneci/index.md) +- [`flux`](modules/manager/flux/index.md) - [`gitlabci`](modules/manager/gitlabci/index.md) - [`helm-requirements`](modules/manager/helm-requirements/index.md) +- [`helm-values`](modules/manager/helm-values/index.md) - [`helmfile`](modules/manager/helmfile/index.md) - [`helmv3`](modules/manager/helmv3/index.md) - [`kubernetes`](modules/manager/kubernetes/index.md) @@ -3656,6 +3764,12 @@ every 3 months on the first day of the month * 0 2 * * ``` + +!!! warning + You _must_ keep the number and the `am`/`pm` part _together_! + Correct: `before 5am`, or `before 5:00am`. + Wrong: `before 5 am`, or `before 5:00 am`. + !!! warning For Cron schedules, you _must_ use the `*` wildcard for the minutes value, as Renovate does not support minute granularity. @@ -3745,9 +3859,16 @@ If you want to enforce grouped package updates, you need to set this option to ` ## separateMinorPatch -By default, Renovate does not distinguish between "patch" (e.g. 1.0.x) and "minor" (e.g. 1.x.0) releases - it groups them together. -E.g., if you are running version 1.0.0 of a package and both versions 1.0.1 and 1.1.0 are available then Renovate will raise a single PR for version 1.1.0. -If you wish to distinguish between patch and minor upgrades, for example if you wish to automerge patch but not minor, then you can configure this option to `true`. +By default, Renovate groups `patch` (`1.0.x`) and `minor` (`1.x.0`) releases into a single PR. +For example: you are running version `1.0.0` of a package, which has two updates: + +- `1.0.1`, a `patch` type update +- `1.1.0`, a `minor` type update + +By default, Renovate creates a single PR for the `1.1.0` version. + +If you want Renovate to create _separate_ PRs for `patch` and `minor` upgrades, set `separateMinorPatch` to `true`. +Getting separate updates from Renovate can be handy when you want to, for example, automerge `patch` updates but manually merge `minor` updates. ## separateMultipleMajor @@ -3755,6 +3876,21 @@ Configure this to `true` if you wish to get one PR for every separate major vers For example, if you are on webpack@v1 currently then default behavior is a PR for upgrading to webpack@v3 and not for webpack@v2. If this setting is true then you would get one PR for webpack@v2 and one for webpack@v3. +## separateMultipleMinor + +Enable this for dependencies when it is important to split updates into separate PRs per minor release stream (e.g. `python`). + +For example, if you are on `python@v3.9.0` currently, then by default Renovate creates a PR to upgrade you to the latest version such as `python@v3.12.x`. +By default, Renovate skips versions in between, like `python@v3.10.x`. + +But if you set `separateMultipleMinor=true` then you get separate PRs for each minor stream, like `python@3.9.x`, `python@v3.10.x` and `python@v3.11.x`, etc. + +## skipInstalls + +By default, Renovate will use the most efficient approach to updating package files and lock files, which in most cases skips the need to perform a full module install by the bot. +If this is set to false, then a full install of modules will be done. +This is currently applicable to `npm` only, and only used in cases where bugs in `npm` result in incorrect lock files being updated. + ## statusCheckNames You can customize the name/context of status checks that Renovate adds to commits/branches/PRs. @@ -3981,3 +4117,7 @@ To disable the vulnerability alerts feature, set `enabled=false` in a `vulnerabi } } ``` + + +!!! note + If you want to raise only vulnerability fix PRs, you may use the `security:only-security-updates` preset. diff --git a/docs/usage/dependency-pinning.md b/docs/usage/dependency-pinning.md index 3d7f4605bfe41b..694b0e54c05d87 100644 --- a/docs/usage/dependency-pinning.md +++ b/docs/usage/dependency-pinning.md @@ -6,7 +6,7 @@ description: The pros and cons of dependency pinning for JavaScript/npm # Should you Pin your JavaScript Dependencies? Once you start using a tool/service like Renovate, probably the biggest decision you need to make is whether to "pin" your dependencies instead of using SemVer ranges. -The answer is "It's your choice", but we can certainly make some generalisations/recommendations to help you. +The answer is "It's your choice", but we can certainly make some generalizations/recommendations to help you. If you do not want to read the in-depth discussion, you can skip ahead to our recommendations in the ["Recommendations" section](#recommendations). @@ -72,7 +72,7 @@ By pinning dependencies you know exactly what you are running, and you know exac Now consider a similar theoretical scenario where `foobar@1.2.0` is faulty, but it is _not_ caught by any of your automated tests. This is more common and more dangerous. -If you were using SemVer ranges then this new version of `foobar` will likely be deployed to production automatically one day, sometime after which you notice errors and realise you need to fix it. +If you were using SemVer ranges then this new version of `foobar` will likely be deployed to production automatically one day, sometime after which you notice errors and realize you need to fix it. Like before, you need to manually work out which dependency caused it - assuming you guess correctly that it was a new dependency version at fault - and pin it manually by editing `package.json` one dependency at a time. Alternatively, if you were instead pinning `foobar` then you would get a PR for `foobar@1.2.0` which awaits your approval. @@ -186,8 +186,8 @@ You could even be running `yarn upgrade` regularly to be getting _indirect_ pack So the lock file does not solve the same SemVer problems that pinning solves - but it compliments it. For this reason our usual recommendation is using a lock file regardless of whether you pin dependencies or not, and pinning even if you have a lock file. -Do not forget though that our motto is "Flexible, so you do not need to be", so go ahead and configure however you want. -Also, we are open to ideas for how to make lock file updates more "visible" too. +But you may also go ahead and configure however you want. +Also, we're open to ideas for how to make lock file updates more "visible" too. e.g. are you interested in a Renovate feature where you get a lockfile-only PR any time a direct dependency gets an in-range update? ## What about indirect/sub-dependencies? diff --git a/docs/usage/docker.md b/docs/usage/docker.md index bb68a0b41855e9..3eea02c099cbfc 100644 --- a/docs/usage/docker.md +++ b/docs/usage/docker.md @@ -9,6 +9,7 @@ Renovate supports upgrading dependencies in various types of Docker definition f - Docker's `Dockerfile` files - Docker Compose `docker-compose.yml`, `compose.yml` files +- Visual Studio Code dev containers and GitHub Codespaces images and features - CircleCI config files - Kubernetes manifest files - Ansible configuration files @@ -120,13 +121,13 @@ For example: Renovate understands [Ubuntu release code names](https://wiki.ubuntu.com/Releases) and will offer upgrades to the latest LTS release. You must only use the _first_ term of the code name in _lowercase_. -So use `jammy` for the Jammy Jellyfish release. +So use `noble` for the Noble Numbat release. For example, Renovate will offer to upgrade the following `Dockerfile` layer: ```diff -- FROM ubuntu:focal -+ FROM ubuntu:jammy +- FROM ubuntu:jammy ++ FROM ubuntu:noble ``` ### Debian codenames @@ -383,7 +384,7 @@ To get access to the token a custom Renovate Docker image is needed that include The Dockerfile to create such an image can look like this: ```Dockerfile -FROM renovate/renovate:37.214.0 +FROM renovate/renovate:37.356.1 # Include the "Docker tip" which you can find here https://cloud.google.com/sdk/docs/install # under "Installation" for "Debian/Ubuntu" RUN ... diff --git a/docs/usage/examples/self-hosting.md b/docs/usage/examples/self-hosting.md index 5e302b9846cd90..643c3fa2baf231 100644 --- a/docs/usage/examples/self-hosting.md +++ b/docs/usage/examples/self-hosting.md @@ -384,7 +384,7 @@ spec: ## Logging If you are ingesting/parsing logs into another system then we recommend you set `LOG_LEVEL=debug` and `LOG_FORMAT=json` in your environment variables. -Debug logging is usually needed for any debugging, while JSON format will mean that the output is parseable. +Debug logging is usually needed for any debugging, while JSON format will mean that the output is parsable. ### About the log level numbers @@ -416,7 +416,10 @@ This means Renovate can safely connect to systems using that certificate or cert Helper programs like Git and npm use the system trust store. For those programs to trust a self-signed certificate you must add it to the systems trust store. -On Ubuntu/Debian and many Linux-based systems, this can be done by copying the self-signed certificate (e.g. `self-signed-certificate.crt`) to `/usr/local/share/ca-certificates/` and running [`update-ca-certificates`](https://manpages.ubuntu.com/manpages/xenial/man8/update-ca-certificates.8.html) to update the system trust store afterwards. +On Ubuntu/Debian and many Linux-based systems, this can be done by: + +1. copying the self-signed certificate (e.g. `self-signed-certificate.crt`) to `/usr/local/share/ca-certificates/` +1. and running [`update-ca-certificates`](https://manpages.ubuntu.com/manpages/noble/man8/update-ca-certificates.8.html) to update the system trust store afterwards ### Renovate Docker image diff --git a/docs/usage/faq.md b/docs/usage/faq.md index d1a136a2c9ac4a..4faf08372df96f 100644 --- a/docs/usage/faq.md +++ b/docs/usage/faq.md @@ -36,11 +36,34 @@ The maintainers do not follow any release schedule or release cadence. This means the Mend Renovate App can lag a few hours to a week behind the open source version. Major releases of Renovate are held back until the maintainers are reasonably certain it works for most users. +## How can I see which version the Mend Renovate app is using? + +Follow these steps to see which version the Mend Renovate app is on: + +1. Go to the [Mend Developer Portal](https://developer.mend.io/) +1. Sign in to the Renovate app with your GitHub or Bitbucket account +1. Select your organization +1. Select a installed repository +1. Select a job from the _Recent jobs_ overview +1. Select the _Info_ Log Level from the dropdown menu +1. You should see something like this: + + ``` + INFO: Repository started + { + "renovateVersion": "37.356.1" + } + ``` + + +!!! tip + The PRs that Renovate creates have a link to the "repository job log" in the footer of the PR body text. + ## Renovate core features not supported on all platforms | Feature | Platforms which lack feature | See Renovate issue(s) | | --------------------- | ---------------------------------------------------------- | ------------------------------------------------------------ | -| Dependency Dashboard | Azure, Bitbucket, Bitbucket Server | [#9592](https://github.com/renovatebot/renovate/issues/9592) | +| Dependency Dashboard | Azure, Bitbucket, Bitbucket Server, Gerrit | [#9592](https://github.com/renovatebot/renovate/issues/9592) | | The Mend Renovate App | Azure, Bitbucket, Bitbucket Server, Forgejo, Gitea, GitLab | | ## Major platform features not supported by Renovate diff --git a/docs/usage/getting-started/private-packages.md b/docs/usage/getting-started/private-packages.md index 13cb543b0ada8f..80383833c71dc8 100644 --- a/docs/usage/getting-started/private-packages.md +++ b/docs/usage/getting-started/private-packages.md @@ -616,4 +616,4 @@ For instructions on this, see the above section on encrypting secrets for the Me ### hostRules configuration using environment variables -Self-hosted users can enable the option [`detectHostRulesFromEnv`](../self-hosted-configuration.md#detectHostRulesFromEnv) to configure the most common types of `hostRules` via environment variables. +Self-hosted users can enable the option [`detectHostRulesFromEnv`](../self-hosted-configuration.md#detecthostrulesfromenv) to configure the most common types of `hostRules` via environment variables. diff --git a/docs/usage/index.md b/docs/usage/index.md new file mode 100644 index 00000000000000..bcf0dd8856e421 --- /dev/null +++ b/docs/usage/index.md @@ -0,0 +1,91 @@ +![Renovate banner](https://app.renovatebot.com/images/whitesource_renovate_660_220.jpg){ loading=lazy } + +# Renovate documentation + +Automated dependency updates. +Multi-platform and multi-language. + +## Why use Renovate? + + + + +
+ +- :octicons-git-pull-request-24:{ .lg .middle } __Automatic updates__ + + --- + + Get pull requests to update your dependencies and lock files. + +- :octicons-calendar-24:{ .lg .middle } __On your schedule__ + + --- + + Reduce noise by scheduling when Renovate creates PRs. + +- :octicons-package-24:{ .lg .middle } __Works out of the box__ + + --- + + Renovate finds relevant package files automatically, including in monorepos. + +- :octicons-goal-24:{ .lg .middle } __How you like it__ + + --- + + You can customize the bot's behavior with configuration files. + +- :octicons-share-24:{ .lg .middle } __Share your configuration__ + + --- + + Share your configuration with ESLint-like config presets. + +- :octicons-sync-24:{ .lg .middle } __Out with the old, in with the new__ + + --- + + Get replacement PRs to migrate from a deprecated dependency to the community suggested replacement, works with _most_ managers, see [issue 14149](https://github.com/renovatebot/renovate/issues/14149) for exceptions. + +- :octicons-tools-24:{ .lg .middle } __Open source__ + + --- + + Renovate is licensed under the [GNU Affero General Public License](https://github.com/renovatebot/renovate/blob/main/license). + +
+ + + + +## Supported Platforms + +Renovate works on these platforms: + +- [GitHub (.com and Enterprise Server)](./modules/platform/github/index.md) +- [GitLab (.com and CE/EE)](./modules/platform/gitlab/index.md) +- [Bitbucket Cloud](./modules/platform/bitbucket/index.md) +- [Bitbucket Server](./modules/platform/bitbucket-server/index.md) +- [Azure DevOps](./modules/platform/azure/index.md) +- [AWS CodeCommit](./modules/platform/codecommit/index.md) +- [Gitea and Forgejo](./modules/platform/gitea/index.md) +- [Gerrit (experimental)](./modules/platform/gerrit/index.md) + +## Who Uses Renovate? + +Renovate is used by: + +![Renovate Matrix](./assets/images/matrix.png){ loading=lazy } + +## Ways to run Renovate + +You can run Renovate as: + +- an [Open Source npm package](https://www.npmjs.com/package/renovate) +- a [pre-built Open Source image on Docker Hub](https://hub.docker.com/r/renovate/renovate) + +Or you can use [the Mend Renovate App](https://github.com/marketplace/renovate) which is hosted by [Mend](https://www.mend.io/). + +[Install the Mend Renovate app for GitHub](https://github.com/marketplace/renovate){ .md-button .md-button--primary } +[Check out our tutorial](https://github.com/renovatebot/tutorial){ .md-button } diff --git a/docs/usage/javascript.md b/docs/usage/javascript.md index 56496d2cc9c37f..f499e2dc84f53d 100644 --- a/docs/usage/javascript.md +++ b/docs/usage/javascript.md @@ -1,10 +1,10 @@ --- title: JavaScript -description: JavaScript (npm/Yarn) Package Manager Support in Renovate +description: JavaScript (npm/Yarn/pnpm/Bun) Package Manager Support in Renovate --- # JavaScript Renovate supports upgrading JavaScript dependencies specified in `package.json` files. -`npm`, `yarn`, and `pnpm` are all supported. +`npm`, `yarn`, `pnpm` and `bun` are all supported. diff --git a/docs/usage/key-concepts/changelogs.md b/docs/usage/key-concepts/changelogs.md index 435e923f779817..b49a64dc548ce8 100644 --- a/docs/usage/key-concepts/changelogs.md +++ b/docs/usage/key-concepts/changelogs.md @@ -98,11 +98,11 @@ If your repository uses the monorepo pattern make sure _each_ `package.json` fil ### maven package maintainers -Read [`maven` datasource, making your changelogs fetchable](https://docs.renovatebot.com/modules/datasource/maven/#making-your-changelogs-fetchable). +Read [`maven` datasource, making your changelogs fetchable](../modules/datasource/maven/index.md#making-your-changelogs-fetchable). ### Docker image maintainers -Read the [Docker datasource](https://docs.renovatebot.com/modules/datasource/docker/) docs. +Read the [Docker datasource](../modules/datasource/docker/index.md) docs. ### Nuget package maintainers diff --git a/docs/usage/key-concepts/how-renovate-works.md b/docs/usage/key-concepts/how-renovate-works.md index 3869144fe7c30f..20d3e3eef3d653 100644 --- a/docs/usage/key-concepts/how-renovate-works.md +++ b/docs/usage/key-concepts/how-renovate-works.md @@ -23,7 +23,7 @@ Renovate's modules are: - [datasource](../modules/datasource/index.md) - [manager](../modules/manager/index.md) - [platform](../modules/platform/index.md) -- [versioning](../modules/versioning.md) +- [versioning](../modules/versioning/index.md) Renovate uses these modules in order: diff --git a/docs/usage/language-constraints-and-upgrading.md b/docs/usage/language-constraints-and-upgrading.md new file mode 100644 index 00000000000000..3f1e4501451904 --- /dev/null +++ b/docs/usage/language-constraints-and-upgrading.md @@ -0,0 +1,106 @@ +# Language constraints and upgrading + +## Package releases have language constraints + +Many ecosystems have the concept where each release of a package has its own language "constraint". +For example, a npm package may support Node.js 18 and 20 in its `v1` releases and Node.js 20 and 22 from `v2.0.0` onwards. + +In an ideal scenario: + +- Package files allow a project to show its supported language constraints, and +- Package registries allow packages to show the supported language constraints per release + +## Restricting upgrades to compatible releases + +By default Renovate _does not_ apply language constraints to upgrades. +This means Renovate will propose "any" stable upgrade. +Renovate will _not_ check if the language version you're using actually supports that upgrade. +In certain ecosystems, changes to language constraints are made with a major release, and are documented in the release notes. +So Renovate's default behavior may be okay in those ecosystems. +For other ecosystems Renovate's default behavior may seem _wrong_. + +As a Renovate user, you can opt into strict compatibility filtering by setting `constraintsFiltering=strict`. +Before you set `constraintsFiltering=strict`, you should: + +- understand the limitations of this setting +- understand why `constraintsFiltering=strict` is _not_ the default behavior + +Please keep reading to learn more. + +## Language constraint updating + +The first challenge is that Renovate may not yet support the ability to update your language constraints in an automated manner, and even when it does, users may not understand how many updates are depending on it. + +For example: a Node.js project has set its `engines` field to `"node": "^18.0.0 || ^20.0.0"`. + +Should Renovate _skip_ Node.js `v21` because it is a non-LTS release? +When Node.js `v22` releases, should Renovate add it to your `engines`, or wait until `v22` becomes the LTS version? +When Node.js `v18` is EOL, should Renovate drop it from the `engines` field? + +Renovate can not guess what users want. +Users have strong and different opinions on what Renovate should do for each example listed above. + +Also, even _if_ Renovate guesses right or adds advanced capabilities to allow this to be configurable: users might still wait on any of these "major" upgrades for months. +If a project waits to create or merge the update to drop Node.js `v18` from `engines`, then they can _not_ upgrade to any new versions of library dependencies. +Those library dependencies may have dropped support for Node.js `v18` already. + +## Strict filtering limitations + +Let's go back to the Node.js project which has its `engines` field set to `"node": "^18.0.0 || ^20.0.0"`. + +Now also consider a library which sets its `engines` field to `"node": "^18.12.0 || ^20.9.0"` because the library only supports "LTS releases" of Node.js. +Strictly speaking, this library is _not_ compatible with the project above, because the project has _wider requirements_ for their Node versions. +This means Renovate holds back any upgrades for it. +Should Renovate somehow "think" and _assume_ that this narrower `engines` support is actually OK? +What if the project _already_ used a current version of this library "in a way that's not officially supported"? + +A second problem is that if: + +- Renovate can _not_ update the language constraints, or +- a user _ignores_ or does not see the language upgrade + +Then the user may not know that many dependencies are out of date, because Renovate is not creating PRs. +For example: a project may have 10 dependencies, and 8 of those have updates. +But all 8 dependencies need the project to update its language constraints _first_. +The project administrator thinks they are up to date, because Renovate is not creating PRs, but 80% of their dependencies are outdated. + +In short, users who set `constraintsFiltering=strict` often do not understand how _strict_ that setting is and how many releases it will _filter out_. + +## Transitive constraint limitations + +Often a library sets language constraints (like the `engines` examples above), and then depend on libraries with _narrower_ contraints, like `"node": "^20.0.0"`. +In cases like these, Renovate "trusts" the declaration of the library and may create a update, even _with_ strict constraints filtering. + +For some package managers, like `npm`, this incompatibility will _not_ be detected or warned about (even during lock file generation), but this may not be a problem for your application. +Other package managers, like Poetry, may detect and warn about incompatible language constraints during lock file generation, which Renovate reports as an "Artifacts update error". + +## Applying constraints through config + +You can set language constraints in the Renovate config. +For example: + +```json title="Renovate config with Node.js constraints" +{ + "constraints": { + "node": "^18.0.0 || >=20.0.0" + } +} +``` + +You may need to set constraints in the Renovate config when: + +- The package manager of the project does not support constraints declarations, or +- The project has not declared any constraints, or +- You want Renovate to use _different_ constraints to what's declared in the _project_ + +Renovate will _not_ create "update" PRs to update any of these versions once they become outdated, so you must update those by hand. +For this reason, setting constraints manually in the Renovate config is _undesirable_. +We prefer to fix problems in Renovate itself, instead of you setting constraints. + +## Future Work + +Please start, or join, a GitHub Discussion if you are interested in this topic. +Subtopics include: + +- Improving language constraints update automation in package files +- Improving versioning calculations of "subset" (is range A a subset of range B) diff --git a/docs/usage/logo-brand-guidelines.md b/docs/usage/logo-brand-guidelines.md new file mode 100644 index 00000000000000..7acab6425cd537 --- /dev/null +++ b/docs/usage/logo-brand-guidelines.md @@ -0,0 +1,36 @@ +# Logo and brand guidelines + +This page explains how you may use the Renovate name, logo and branding. + +## Do not pretend to be the real Renovate app + +Avoid using our name, logo, or branding in a way that causes people to think you are the real Renovate app on a public platform. +For example: do _not_ call your self-hosted version something like @realrenovatebot on GitHub. + +## Do not pretend to be a Renovate developer + +Avoid using our name, logo, or branding in a way that causes people to think you are a developer of Renovate. + +## Allowed uses of the Renovate name + +You are allowed to use the Renovate name: + +- to refer to the official Renovate app +- as a nickname/shorthand, in contexts where it is clear you are referring to your self-hosted version + +## Allowed uses of the Renovate logo + +You are allowed to use our logo as: + +- an icon in your repository readme, that says you are using Renovate +- part of a badge in your repository readme, that says you are using Renovate +- an avatar image for your self-hosted version of Renovate, but give your bot a _different_ name + +## Allowed uses of the Renovate branding + +Do not use our banner images. + +## We keep the rights to our logo, name, and branding + +You may only use our logo, name and branding as described in this guideline. +We keep the rights to our logo, name and branding. diff --git a/docs/usage/modules/.pages b/docs/usage/modules/.pages index 74686a6979df56..2c965ed561bdd3 100644 --- a/docs/usage/modules/.pages +++ b/docs/usage/modules/.pages @@ -1,3 +1,6 @@ title: Renovate Modules +nav: + - Introduction: index.md + - ... order: asc sort_type: natural diff --git a/docs/usage/modules/index.md b/docs/usage/modules/index.md new file mode 100644 index 00000000000000..50f7fe12b8805c --- /dev/null +++ b/docs/usage/modules/index.md @@ -0,0 +1,14 @@ +--- +title: Modules introduction +--- + +# Modules introduction + +Renovate modules, please select a subsection. + +## Supported modules + +- [Datasources](./datasource/index.md) +- [Managers](./manager/index.md) +- [Platform](./platform/index.md) +- [Versioning](./versioning/index.md) diff --git a/docs/usage/modules/versioning/.pages b/docs/usage/modules/versioning/.pages new file mode 100644 index 00000000000000..4a59c0f2edb4ed --- /dev/null +++ b/docs/usage/modules/versioning/.pages @@ -0,0 +1,7 @@ +title: Versionings +nav: + - Introduction: index.md + - ... +collapse_single_pages: true +order: asc +sort_type: natural diff --git a/docs/usage/modules/versioning.md b/docs/usage/modules/versioning/index.md similarity index 100% rename from docs/usage/modules/versioning.md rename to docs/usage/modules/versioning/index.md diff --git a/docs/usage/nuget.md b/docs/usage/nuget.md index 48d424d2d43037..3547b0ed024e54 100644 --- a/docs/usage/nuget.md +++ b/docs/usage/nuget.md @@ -78,15 +78,26 @@ So Renovate behaves like the official NuGet client. #### v3 feed URL not ending with index.json -If a `v3` feed URL does not end with `index.json`, you must append `#protocolVersion=3` to the registry URL: +If a `v3` feed URL does not end with `index.json`, you must specify the version explicitly. -```json -{ - "nuget": { - "registryUrls": ["http://myV3feed#protocolVersion=3"] +- If the feed is defined in a `NuGet.config` file set the `protocolVersion` attribute to `3`: + + ```xml + + + + + ``` + +- If the feed is defined via Renovate configuration append `#protocolVersion=3` to the registry URL: + + ```json + { + "nuget": { + "registryUrls": ["http://myV3feed#protocolVersion=3"] + } } -} -``` + ``` You may need this workaround when you use the JFrog Artifactory. diff --git a/docs/usage/python.md b/docs/usage/python.md index 52e8d6a8378d89..01bbe3f723ede0 100644 --- a/docs/usage/python.md +++ b/docs/usage/python.md @@ -23,24 +23,6 @@ Legacy versions with the `===` prefix are ignored. 1. Renovate searches for the latest version on [PyPI](https://pypi.org/) to decide if there are upgrades 1. If the source package includes a GitHub URL as its source, and has a "changelog" file _or_ uses GitHub releases, a Release Note will be embedded in the generated PR -## Alternative file names - -For the `pip_requirements` manager, the default file matching regex for `requirements.txt` follows common file name conventions. - -It will match `requirements.txt` and `requirements.pip`, and any file in the format `requirements-*.txt` or `requirements-*.pip`, to allow for common filename patterns such as `requirements-dev.txt`. - -But Renovate may not find all your files. - -You can tell Renovate where to find your file(s) by setting your own `fileMatch` regex: - -```json title="Setting a custom fileMatch regex" -{ - "pip_requirements": { - "fileMatch": ["my/specifically-named.file", "\\.requirements$"] - } -} -``` - ## Alternate registries By default, Renovate checks for upgrades on the `pypi.org` registry. diff --git a/docs/usage/release-notes-for-major-versions.md b/docs/usage/release-notes-for-major-versions.md new file mode 100644 index 00000000000000..d42962fe917bc0 --- /dev/null +++ b/docs/usage/release-notes-for-major-versions.md @@ -0,0 +1,157 @@ +# Release notes for major versions of Renovate + +It can be hard to keep track of the changes between major versions of Renovate. +To help you, we've listed the breaking changes, plus the developer commentary for the latest major releases. + +The most recent versions are always at the top of the page. +This is because recent versions may revert changes made in an older version. +You also don't have to scroll to the bottom of the page to find the latest release notes. + +## Version 37 + +### Breaking changes for 37 + +- **npm:** drop explicit lerna support + +### Commentary for 37 + +We switched from "merge" strategy to "hunt" strategy to match with how Maven works. + +Lerna v7 does not need our explicit support anymore, so we dropped it. +If you're on a version of Lerna before v7, you should prioritize upgrading to v7. + +### Link to release notes for 37 + +[Release notes for `v37` on GitHub](https://github.com/renovatebot/renovate/releases/tag/37.0.0). + +## Version 36 + +### Breaking changes for 36 + +- postUpgradeTasks.fileFilters is now optional and defaults to all files +- `languages` are now called `categories` instead. Use `matchCategories` in `packageRules` +- Node v19 is no longer supported +- **datasource:** `semver-coerced` is now the default versioning +- **presets:** Preset `config:base` is now called `config:recommended` (will be migrated automatically) +- remove `BUILDPACK` env support +- **package-rules:** `matchPackageNames` now matches both `depName` (existing) and `packageName` (new) and warns if only `depName` matches +- **release-notes:** Release notes won't be fetched early for `commitBody` insertion unless explicitly configured with `fetchReleaseNotes=branch` +- `dockerImagePrefix` is now replaced by `dockerSidecarImage` +- `matchPaths` and `matchFiles` are now combined into `matchFileNames`, supporting exact match and glob-only. The "any string match" functionality of `matchPaths` is now removed +- **presets:** v25 compatibility for language-based branch prefixes is removed +- **npm:** Rollback PRs will no longer be enabled by default for npm (they are now disabled by default for all managers) +- **post-upgrade-tasks:** dot files will now be included by default for all minimatch results +- **platform/gitlab:** GitLab `gitAuthor` will change from the account's "email" to "commit_email" if they are different +- **automerge:** Platform automerge will now be chosen by default whenever automerge is enabled +- Post upgrade templating is now allowed by default, as long as the post upgrade task command is itself already allowed +- Official Renovate Docker images now use the "slim" approach with `binarySource=install` by default. e.g. `renovate/renovate:latest` is the slim image, not full +- The "full" image is now available via the tag `full`, e.g. `renovate/renovate:37-full`, and defaults to `binarySource=global` (no dynamic installs) +- Third party tools in the full image have been updated to latest/LTS major version + +### Commentary for 36 + +If you're self-hosting Renovate, pay particular attention to: + +- Do you want to run the full, or slim versions of the image? We have switched the defaults (latest is now slim, not full) +- Have you configured `dockerImagePrefix`? If so then you need to use `dockerSidecarImage` instead +- If you're using `config:base` in your `onboardingConfig` then switch to `config:recommended` +- `gitAuthor` may change if you're on GitLab and have a different commit email for your bot account. If so then configure `gitIgnoredAuthors` with the old email + +### Link to release notes for 36 + +[Release notes for `v36` on GitHub](https://github.com/renovatebot/renovate/releases/tag/36.0.0). + +## Version 35 + +### Breaking changes for 35 + +- require NodeJS v18.12+ ([#20838](https://github.com/renovatebot/renovate/pull/20838)) +- **config:** Forked repos will now be processed automatically if `autodiscover=false`. `includeForks` is removed and replaced by new option `forkProcessing` +- Internal checks such as `renovate/stability-days` will no longer count as passing/green, meaning that actions such as `automerge` won't occur if the only checks are Renovate internal ones. Set `internalChecksAsSuccess=true` to restore existing behavior +- **versioning:** default versioning is now `semver-coerced`, instead of `semver` +- **datasource/github-releases:** Regex Manager configurations relying on the github-release data-source with digests will have different digest semantics. The digest will now always correspond to the underlying Git SHA of the release/version. The old behavior can be preserved by switching to the github-release-attachments datasource +- **versioning:** bump short ranges to version ([#20494](https://github.com/renovatebot/renovate/pull/20494)) +- **config:** `containerbase/` account used for sidecar containers instead of `renovate/` +- **go:** Renovate will now use go's default `GOPROXY` settings. To avoid using the public proxy, configure `GOPROXY=direct` +- **datasource/npm:** Package cache will include entries for up to 24 hours after the last lookup. Set `cacheHardTtlMinutes=0` to revert to existing behavior +- **config:** Renovate now defaults to applying hourly and concurrent PR limits. To revert to unlimited, configure them back to `0` +- **config:** Renovate will now default to updating locked dependency versions. To revert to previous behavior, configure `rangeStrategy=replace` +- **config:** PyPI releases will no longer be filtered by default based on `constraints.python` compatibility. To retain existing functionality, set `constraintsFiltering=strict` + +### Commentary for 35 + +Most of these changes will be invisible to the majority of users. +They may be "breaking" (change of behavior) but good changes of defaults to make. + +The biggest change is defaulting `rangeStrategy=auto` to use `update-lockfile` instead of `replace`, which impacts anyone using the recommended `config:base`. +This will mean that you start seeing some "lockfile-only" PRs for in-range updates, such as updating `package-lock.json` when a range exists in `package.json`. + +### Link to release notes for 35 + +[Release notes for `v35` on GitHub](https://github.com/renovatebot/renovate/releases/tag/35.0.0). + +## Version 34 + +### Breaking changes for 34 + +- Revert `branchNameStrict` to `false` + +### Commentary for 34 + +Here comes v34 hot on the heels of v33. +We decided to issue another breaking change to revert one of the breaking changes in v33. + +If you are upgrading from v32 to v34 then it means that the setting for `branchNameStrict` remains as `false` and you don't need to worry about that. + +If you already upgraded from v32 to v33 then you have a decision to make first: + +- set `branchNameStrict` to `true` (like in v33), +- or let it set back to `false` (like in v32). + +Strict branch naming meant that all special characters other than letters, numbers and hyphens were converted to hyphens and then deduplicated, e.g. a branch which in v32 was like `renovate/abc.def-2.x` would become `renovate/abc-def-2-x` in v33. +If you prefer to revert back to the old way then that will happen automatically in v34. +If you prefer to keep the way in v33 because you already had a bunch of PRs closed and reopened due to branch names, and don't want to do that again, then add `branchNameStrict: false` to your bot config or your shared config before updating to v34. + +Apologies to anyone negatively affected by this v33 change. + +### Link to release notes for 34 + +[Release notes for `v34` on GitHub](https://github.com/renovatebot/renovate/releases/tag/34.0.0). + +## Version 33 + +### Breaking changes for 33 + +- Node 16 is the required runtime for Renovate +- [NOTE: This was reverted in `v34`] **config:** `branchNameStrict` default value is now `true` +- **config:** `internalChecksFilter` default value is now `"strict"` +- **config:** `ignoreScripts` default value is now `true`. If `allowScripts=true` in global config, `ignoreScripts` must be set to `false` in repo config if you want all repos to run scripts +- **config:** `autodiscover` filters can no longer include commas +- **config:** boolean variables must be `true` or `false` when configured in environment variables, and errors will be thrown for invalid values. Previously invalided values were ignored and treated as `false` +- **datasource/go:** `git-tags` datasource will be used as the fallback instead of `github-tags` if a go package's host type is unknown +- **jsonnet-bundler:** `depName` now uses the "absolute import" format (e.g. `bar`-> `github.com/foo/bar/baz-wow`) +- **azure-pipelines:** azure-pipelines manager is now disabled by default +- **github:** No longer necessary to configure forkMode. Forking mode is now experimental +- Users of `containerbase` images (such as official Renovate images) will now have dynamic package manager installs enabled by default +- Dependencies are no longer automatically pinned if `rangeStrategy=auto`, pinning must be opted into using `rangeStrategy=pin` + +### Commentary for 33 + +This release contains some changes of default values/behavior: + +- `internalChecksFilter` will now default to `strict`, meaning that updates will be withheld by default when internal status checks are pending. This should reduce the number of "non-actionable" Pull Requests you get +- `azure-pipelines` manager is disabled by default, because its primary datasource can unfortunately suggest updates which aren't yet installable. Users should opt into this manager once they know the risks +- `binarySource=install` will now be used instead of `global` whenever Renovate is run within a "containerbase" image. This means dynamic installation of most package managers and languages +- Dependencies will no longer be pinned by default if `rangeStrategy=auto`. While we recommend pinning dependencies, we decided users should opt into this more explicitly + +And two major features! + +- AWS CodeCommit platform support +- OpenTelemetry support + +Both the above are considered "experimental". +Please test them out and let us know your feedback - both positive or negative - so that we can progress them to fully available. + +### Link to release notes for 33 + +[Release notes for `v33` on GitHub](https://github.com/renovatebot/renovate/releases/tag/33.0.0). diff --git a/docs/usage/security-and-permissions.md b/docs/usage/security-and-permissions.md index 87eab0e83f2040..4759e7710e679a 100644 --- a/docs/usage/security-and-permissions.md +++ b/docs/usage/security-and-permissions.md @@ -65,6 +65,107 @@ For example, if you have an `npm` package and do not configure a private registr You could avoid this by configuring private registries but such registries need to query public registries anyway. We do not know of any public registries which reverse lookup IP addresses to associate companies with packages. +#### Security awareness for self-hosted Renovate instances + +##### Introduction + +Before you start self-hosting Renovate you must understand the security implications associated with monitoring and updating repositories. +The process that Renovate uses to update dependencies runs under the same user context as the Renovate process itself. +This also means the process has the same level of access to information and resources as the user context! + +##### Trusting Repository Developers + +All self-hosted Renovate instances must operate under a trust relationship with the developers of the monitored repositories. +This has the following implications: + +- Access to information +- Execution of code + +Keep reading to learn more. + +###### Access to information + +Since the update process runs with the _same_ user privileges as the Renovate process, it inherently has access to the same information and resources. +This includes sensitive data that may be stored within the environment where Renovate is hosted. + +###### Execution of code + +In certain scenarios, code from the monitored repository is executed as part of the update process. +This is particularly true during, for example: + +- `postUpgradeTasks`, where scripts specified by the repository are run +- when a wrapper within the repository is called, like `gradlew` + +These scripts can contain arbitrary code. +This may pose a significant security risk if the repository's integrity is compromised, or if the repository maintainers have malicious intentions. + +Because such insider attack is an inherent and unavoidable risk, the Renovate project will not issue CVEs for such attacks or weaknesses other than in exceptional circumstances. + +##### Centralized logging and sensitive information management + +Centralized logging is key to monitor and troubleshoot self-hosted Renovate environments. +But logging may inadvertently capture and expose sensitive information. +Operations that involve `customEnvVariables`, among others, could expose sensitive data, when logging is used. + +##### Recommendations + +The Renovate maintainers recommend you follow these guidelines. + +###### Vet and monitor repositories + +_Before_ integrating a repository with your self-hosted Renovate instance, thoroughly vet the repository for security and trustworthiness. +This means that you should review the: + +- repository's ownership +- contribution history +- open issues +- open pull requests + +###### Limit permissions + +Configure the environment running Renovate with the principle of least privilege. +Ensure that the Renovate process has only the permissions needed to perform its tasks and no more. +This reduces the impact of any malicious code execution. + +###### Regularly review post-upgrade tasks + +Regularly review the actions taken by `postUpgradeTasks` to make sure they do not execute unnecessary or risky operations. +Consider implementing a review process for changes to these tasks within repositories. + +###### Use security tools + +Employ security tools and practices, like code scanning and vulnerability assessments, on the Renovate configuration _and_ the repositories Renovate manages. +This helps identify potentially malicious code before it is executed. + +###### Securing environment variables + +When configuring `customEnvVariables`: _always_ use Renovate's secrets management syntax `({{ secrets.VAR_NAME }})` to reference sensitive variables securely. +This makes sure that sensitive data is not exposed as plain text. + +###### Logging infrastructure security + +Ensure that the logging infrastructure is configured to handle logs as sensitive data. +This includes measures like: + +- log encryption +- access controls to restrict log viewing to authorized personnel only +- secure storage and transmission of log data + +###### Log review and redaction processes + +Implement rigorous log review mechanisms to regularly scan for and redact sensitive information that might be logged inadvertently. +Automated tools can assist in identifying patterns indicative of sensitive data, such as credentials or personal information, enabling timely redaction or alerting. + +###### Stay informed + +Keep abreast of updates and security advisories related to Renovate itself. +Apply updates promptly to ensure that your self-hosted instances get the latest security enhancements and bug fixes. + +#### Conclusion + +The flexibility and power of self-hosting Renovate also means you must take steps to manage your security. +By understanding the risks associated with repository management and taking steps to mitigate those risks, organizations can maintain a secure and efficient development workflow. + ### Hosted/SaaS (the Mend Renovate App) Users of the Mend Renovate App fall under [Mend's Terms of Service](https://www.mend.io/terms-of-service/) and Privacy Policy. diff --git a/docs/usage/self-hosted-configuration.md b/docs/usage/self-hosted-configuration.md index 21d1c8e941c292..9f9a2f0552d4d8 100644 --- a/docs/usage/self-hosted-configuration.md +++ b/docs/usage/self-hosted-configuration.md @@ -213,6 +213,38 @@ For example: } ``` + +!!! note + On Gitea/Forgejo, you can't use `autodiscoverTopics` together with `autodiscoverNamespaces` because both platforms do not support this. + Topics are preferred and `autodiscoverNamespaces` will be ignored when you configure `autodiscoverTopics` on Gitea/Forgejo. + +## autodiscoverProjects + +You can use this option to filter the list of autodiscovered repositories by project names. +This feature is useful for users who want Renovate to only work on repositories within specific projects or exclude certain repositories from being processed. + +```json title="Example for Bitbucket" +{ + "platform": "bitbucket", + "autodiscoverProjects": ["a-group", "!another-group/some-subgroup"] +} +``` + +The `autodiscoverProjects` config option takes an array of minimatch-compatible globs or RE2-compatible regex strings. +For more details on this syntax see Renovate's [string pattern matching documentation](./string-pattern-matching.md). + +## autodiscoverRepoOrder + +The order method for autodiscover server side repository search. + +> If multiple `autodiscoverTopics` are used resulting order will be per topic not global. + +## autodiscoverRepoSort + +The sort method for autodiscover server side repository search. + +> If multiple `autodiscoverTopics` are used resulting order will be per topic not global. + ## autodiscoverTopics Some platforms allow you to add tags, or topics, to repositories and retrieve repository lists by specifying those topics. @@ -555,6 +587,12 @@ In practice, it is implemented by converting the `force` configuration into a `p This is set to `true` by default, meaning that any settings (such as `schedule`) take maximum priority even against custom settings existing inside individual repositories. It will also override any settings in `packageRules`. +## forkCreation + +This configuration lets you disable the runtime forking of repositories when running in "fork mode". + +Usually you will need to keep this as the default `true`, and only set to `false` if you have some out of band process to handle the creation of forks. + ## forkOrg This configuration option lets you choose an organization you want repositories forked into when "fork mode" is enabled. @@ -577,6 +615,10 @@ If this value is configured then Renovate: Renovate will then create branches on the fork and opens Pull Requests on the parent repository. + +!!! note + Forked repositories will always be skipped when `forkToken` is set, even if `includeForks` is true. + ## gitNoVerify Controls when Renovate passes the `--no-verify` flag to `git`. @@ -636,12 +678,74 @@ Use the `extends` field instead of this if, for example, you need the ability fo When Renovate resolves `globalExtends` it does not fully process the configuration. This means that Renovate does not have the authentication it needs to fetch private things. +## httpCacheTtlDays + +This option sets the number of days that Renovate will cache HTTP responses. +The default value is 90 days. +Value of `0` means no caching. + + +!!! warning + When you set `httpCacheTtlDays` to `0`, Renovate will remove the cached HTTP data. + ## includeMirrors By default, Renovate does not autodiscover repositories that are mirrors. Change this setting to `true` to include repositories that are mirrors as Renovate targets. +## inheritConfig + +When you enable this option, Renovate will look for the `inheritConfigFileName` file in the `inheritConfigRepoName` repository before processing a repository, and read this in as config. + +If the repository is in a nested organization or group on a supported platform such as GitLab, such as `topGroup/nestedGroup/projectName` then Renovate will look in `topGroup/nestedGroup/renovate-config`. + +If `inheritConfig` is `true` but the inherited config file does _not_ exist then Renovate will proceed without warning. +If the file exists but cannot be parsed, then Renovate will raise a config warning issue and abort the job. + +The inherited config may include all valid repository config and these config options: + +- `bbUseDevelopmentBranch` +- `onboarding` +- `onboardingBranch` +- `onboardingCommitMessage` +- `onboardingConfig` +- `onboardingConfigFileName` +- `onboardingNoDeps` +- `onboardingPrTitle` +- `onboardingRebaseCheckbox` +- `requireConfig` + + +!!! note + The above list is prepared manually and may become out of date. + Consult the self-hosted configuration docs and look for `inheritConfigSupport` values there for the definitive list. + +This way organizations can change/control the default behavior, like whether configs are required and how repositories are onboarded. + +We disabled `inheritConfig` in the Mend Renovate App to avoid wasting millions of API calls per week. +This is because each `404` response from the GitHub API due to a missing org inherited config counts as a used API call. +We will add a smart/dynamic approach in future, so that we can selectively enable `inheritConfig` per organization. + +## inheritConfigFileName + +Change this setting if you want Renovate to look for a different file name within the `inheritConfigRepoName` repository. +You may use nested files, for example: `"some-dir/config.json"`. + +## inheritConfigRepoName + +Change this setting if you want Renovate to look in an alternative repository for the inherited config. +The repository must be on the same platform and endpoint, and Renovate's token must have `read` permissions to the repository. + +## inheritConfigStrict + +By default Renovate will silently (debug log message only) ignore cases where `inheritConfig=true` but no inherited config is found. +When you set `inheritConfigStrict=true` then Renovate will abort the run and raise a config error if Renovate can't find the inherited config. + + +!!! warning + Only set this config option to `true` if _every_ organization has an inherited config file _and_ you want to make sure Renovate _always_ uses that inherited config. + ## logContext `logContext` is included with each log entry only if `logFormat="json"` - it is not included in the pretty log output. @@ -651,6 +755,39 @@ If left as default (null), a random short ID will be selected. ## logFileLevel +## mergeConfidenceDatasources + +This feature is applicable only if you have an access token for Mend's Merge Confidence API. + +If set, Renovate will query the merge-confidence JSON API only for datasources that are part of this list. +Otherwise, it queries all the supported datasources (check default value). + +Example: + +```js +modules.exports = { + mergeConfidenceDatasources: ['npm'], +}; +``` + +## mergeConfidenceEndpoint + +This feature is applicable only if you have an access token for Mend's Merge Confidence API. + +If set, Renovate will retrieve Merge Confidence data by querying this API. +Otherwise, it will use the default URL, which is . + +If you use the Mend Renovate Enterprise Edition (Renovate EE) and: + +- have a static merge confidence token that you set via `MEND_RNV_MC_TOKEN` +- _or_ set `MEND_RNV_MC_TOKEN` to `auto` + +Then you must set this variable at the _server_ and the _workers_. + +But if you have specified the token as a [`matchConfidence`](configuration-options.md#matchconfidence) `hostRule`, you only need to set this variable at the _workers_. + +This feature is in private beta. + ## migratePresets Use this if you have repositories that extend from a particular preset, which has now been renamed or removed. @@ -773,7 +910,7 @@ This private key is used to decrypt config files. The corresponding public key can be used to create encrypted values for config files. If you want a UI to encrypt values you can put the public key in a webpage similar to . -To create the key pair with GPG use the following commands: +To create the PGP key pair with GPG use the following commands: - `gpg --full-generate-key` and follow the prompts to generate a key. Name and email are not important to Renovate, and do not configure a passphrase. Use a 4096bit key. @@ -825,6 +962,32 @@ uid Renovate Bot sub rsa4096 2021-09-10 [E] ``` + +!!! note + If you use GnuPG `v2.4` (or newer) to generate the key, then you must disable `AEAD` preferences. + This is needed to allow Renovate to decrypt the encrypted values. + +```bash +❯ gpg --edit-key renovate@whitesourcesoftware.com +gpg> showpref +[ultimate] (1). Renovate Bot + Cipher: AES256, AES192, AES, 3DES + AEAD: OCB, EAX + Digest: SHA512, SHA384, SHA256, SHA224, SHA1 + Compression: ZLIB, BZIP2, ZIP, Uncompressed + Features: MDC, AEAD, Keyserver no-modify + +gpg> setpref AES256 AES192 AES 3DES SHA512 SHA384 SHA256 SHA224 SHA1 ZLIB BZIP2 ZIP +Set preference list to: + Cipher: AES256, AES192, AES, 3DES + AEAD: + Digest: SHA512, SHA384, SHA256, SHA224, SHA1 + Compression: ZLIB, BZIP2, ZIP, Uncompressed + Features: MDC, Keyserver no-modify +Really update the preferences? (y/N) y +gpg> save +``` + - Copy the key ID from the output (`794B820F34B34A8DF32AADB20649CEXAMPLEONLY` in the above example) or run `gpg --list-secret-keys` if you forgot to take a copy @@ -834,7 +997,7 @@ sub rsa4096 2021-09-10 [E] The private key should then be added to your Renovate Bot global config (either using `privateKeyPath` or exporting it to the `RENOVATE_PRIVATE_KEY` environment variable). The public key can be used to replace the existing key in for your own use. -Any encrypted secrets using GPG must have a mandatory organization/group scope, and optionally can be scoped for a single repository only. +Any PGP-encrypted secrets must have a mandatory organization/group scope, and optionally can be scoped for a single repository only. The reason for this is to avoid "replay" attacks where someone could learn your encrypted secret and then reuse it in their own Renovate repositories. Instead, with scoped secrets it means that Renovate ensures that the organization and optionally repository values encrypted with the secret match against the running repository. @@ -849,11 +1012,15 @@ Instead, with scoped secrets it means that Renovate ensures that the organizatio Use this field if you need to perform a "key rotation" and support more than one keypair at a time. Decryption with this key will be tried after `privateKey`. -If you are migrating from the legacy public key encryption approach to use GPG, then move your legacy private key from `privateKey` to `privateKeyOld` and then put your new GPG private key in `privateKey`. -Doing so will mean that Renovate will first try to decrypt using the GPG key but fall back to the legacy key and try that next. +If you are migrating from the legacy public key encryption approach to use a PGP key, then move your legacy private key from `privateKey` to `privateKeyOld` and then put your new PGP private key in `privateKey`. +Doing so will mean that Renovate will first try to decrypt using the PGP key but fall back to the legacy key and try that next. You can remove the `privateKeyOld` config option once all the old encrypted values have been migrated, or if you no longer want to support the old key and let the processing of repositories fail. + +!!! note + Renovate now logs a warning whenever repositories use non-PGP encrypted config variables. + ## privateKeyPath Used as an alternative to `privateKey`, if you want the key to be read from disk instead. @@ -884,6 +1051,25 @@ For TLS/SSL-enabled connections, use the rediss prefix Example URL structure: `rediss://[[username]:[password]]@localhost:6379/0`. +## reportPath + +`reportPath` describes the location where the report is written to. + +If [`reportType`](#reporttype) is set to `file`, then set `reportPath` to a filepath. +For example: `/foo/bar.json`. + +If the value `s3` is used in [`reportType`](#reporttype), then use a S3 URI. +For example: `s3://bucket-name/key-name`. + +## reportType + +Defines how the report is exposed: + +- `` If unset, no report will be provided, though the debug logs will still have partial information of the report +- `logging` The report will be printed as part of the log messages on `INFO` level +- `file` The report will be written to a path provided by [`reportPath`](#reportpath) +- `s3` The report is pushed to an S3 bucket defined by [`reportPath`](#reportpath). This option reuses [`RENOVATE_X_S3_ENDPOINT`](./self-hosted-experimental.md#renovate_x_s3_endpoint) and [`RENOVATE_X_S3_PATH_STYLE`](./self-hosted-experimental.md#renovate_x_s3_path_style) + ## repositories Elements in the `repositories` array can be an object if you wish to define more settings: @@ -908,17 +1094,9 @@ JSON files will be stored inside the `cacheDir` beside the existing file-based p } ``` - -!!! note - [IAM is supported](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-iam.html) when running Renovate within an EC2 instance in an ECS cluster. In this case, no extra environment variables are required. - Otherwise, the following environment variables should be set for the S3 client to work. - -``` - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - AWS_SESSION_TOKEN - AWS_REGION -``` +Renovate uses the [AWS SDK for JavaScript V3](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/welcome.html) to connect to the S3 instance. +Therefore, Renovate supports all the authentication methods supported by the AWS SDK. +Read more about the default credential provider chain for AWS SDK for JavaScript V3 [here](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-credential-providers/#fromnodeproviderchain). !!! tip @@ -991,12 +1169,6 @@ It could then be used in a repository config or preset like so: Secret names must start with an upper or lower case character and can have only characters, digits, or underscores. -## skipInstalls - -By default, Renovate will use the most efficient approach to updating package files and lock files, which in most cases skips the need to perform a full module install by the bot. -If this is set to `false`, then a full installation of modules will be done. -This is currently applicable to `npm` only, and only used in cases where bugs in `npm` result in incorrect lock files being updated. - ## token ## unicodeEmoji @@ -1009,6 +1181,11 @@ For example: `:warning:` will be replaced with `⚠️`. Some cloud providers offer services to receive metadata about the current instance, for example [AWS Instance metadata](https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/ec2-instance-metadata.html) or [GCP VM metadata](https://cloud.google.com/compute/docs/metadata/overview). You can control if Renovate should try to access these services with the `useCloudMetadataServices` config option. +## userAgent + +If set to any string, Renovate will use this as the `user-agent` it sends with HTTP requests. +Otherwise, it will default to `RenovateBot/${renovateVersion} (https://github.com/renovatebot/renovate)`. + ## username You may need to set a `username` if you: diff --git a/docs/usage/self-hosted-experimental.md b/docs/usage/self-hosted-experimental.md index d809b8a5c792c1..1018421c9c7964 100644 --- a/docs/usage/self-hosted-experimental.md +++ b/docs/usage/self-hosted-experimental.md @@ -32,51 +32,6 @@ Skipping the check will speed things up, but may result in versions being return If set to any value, Renovate will always paginate requests to GitHub fully, instead of stopping after 10 pages. -## `RENOVATE_REUSE_PACKAGE_LOCK` - -If set to "false" (string), Renovate will remove any existing `package-lock.json` before trying to update it. - -## `RENOVATE_USER_AGENT` - -If set to any string, Renovate will use this as the `user-agent` it sends with HTTP requests. - -## `RENOVATE_X_AUTODISCOVER_REPO_ORDER` - - -!!! note - For the Forgejo and Gitea platform only. - -The order method for autodiscover server side repository search. - -> If multiple `autodiscoverTopics` are used resulting order will be per topic not global. - -Allowed values: - -- `asc` -- `desc` - -Default value: `asc`. - -## `RENOVATE_X_AUTODISCOVER_REPO_SORT` - - -!!! note - For the Forgejo and Gitea platform only. - -The sort method for autodiscover server side repository search. - -> If multiple `autodiscoverTopics` are used resulting order will be per topic not global. - -Allowed values: - -- `alpha` -- `created` -- `updated` -- `size` -- `id` - -Default value: `alpha`. - ## `RENOVATE_X_DELETE_CONFIG_FILE` If `true` Renovate tries to delete the self-hosted config file after reading it. @@ -93,6 +48,10 @@ If set to any value, Renovate will use the Docker Hub API (`https://hub.docker.c If set to an integer, Renovate will use this as max page number for docker tags lookup on docker registries, instead of the default 20 pages. This is useful for registries which ignores the `n` parameter in the query string and only return 50 tags per page. +## `RENOVATE_X_EAGER_GLOBAL_EXTENDS` + +Resolve and merge `globalExtends` presets before other global config, instead of after. + ## `RENOVATE_X_EXEC_GPID_HANDLE` If set, Renovate will terminate the whole process group of a terminated child process spawned by Renovate. @@ -131,20 +90,14 @@ Suppress the default warning when a deprecated version of Node.js is used to run Skip initializing `RE2` for regular expressions and instead use Node-native `RegExp` instead. -## `RENOVATE_X_MERGE_CONFIDENCE_API_BASE_URL` - -If set, Renovate will query this API for Merge Confidence data. -This feature is in private beta. - -## `RENOVATE_X_MERGE_CONFIDENCE_SUPPORTED_DATASOURCES` +## `RENOVATE_X_NUGET_DOWNLOAD_NUPKGS` -If set, Renovate will query the merge-confidence JSON API only for datasources that are part of this list. -The expected value for this environment variable is a JSON array of strings. +If set to any value, Renovate will download `nupkg` files for determining package metadata. ## `RENOVATE_X_PLATFORM_VERSION` -If set, Renovate will use this string as GitLab server version instead of checking via the GitLab API. -This can be useful when you use the GitLab `CI_JOB_TOKEN` to authenticate Renovate. +Specify this string for Renovate to skip API checks and provide GitLab/Bitbucket server version directly. +Particularly useful with GitLab's `CI_JOB_TOKEN` to authenticate Renovate or to reduce API calls for Bitbucket. Read [platform details](modules/platform/gitlab/index.md) to learn why we need the server version on GitLab. @@ -156,6 +109,10 @@ If set, Renovate will rewrite GitHub Enterprise Server's pagination responses to !!! note For the GitHub Enterprise Server platform only. +## `RENOVATE_X_REPO_CACHE_FORCE_LOCAL` + +If set, Renovate will persist repository cache locally after uploading to S3. + ## `RENOVATE_X_S3_ENDPOINT` If set, Renovate will use this string as the `endpoint` when instantiating the AWS S3 client. @@ -172,3 +129,15 @@ Source: [AWS S3 documentation - Interface BucketEndpointInputConfig](https://doc If set, Renovate will use SQLite as the backend for the package cache. Don't combine with `redisUrl`, Redis would be preferred over SQlite. + +## `RENOVATE_X_SUPPRESS_PRE_COMMIT_WARNING` + +Suppress the pre-commit support warning in PR bodies. + +## `RENOVATE_X_USE_OPENPGP` + +Use `openpgp` instead of `kbpgp` for `PGP` decryption. + +## `RENOVATE_X_YARN_PROXY` + +Configure global Yarn proxy settings if HTTP proxy environment variables are detected. diff --git a/docs/usage/templates.md b/docs/usage/templates.md index 7d942f1a00f453..6468bc05873dc3 100644 --- a/docs/usage/templates.md +++ b/docs/usage/templates.md @@ -110,6 +110,12 @@ Returns `true` if at least one expression is `true`. `{{#if (or isPatch isSingleVersion}}Small update, safer to merge and release.{{else}}Check out the changelog for all versions before merging!{{/if}}` +### includes + +Returns `true` if the value is included on the list given. + +`{{#if (includes labels 'dependencies')}}Production Dependencies{{else}}Not Production Dependencies{{/if}}` + ## Environment variables By default, you can only access a handful of basic environment variables like `HOME` or `PATH`. diff --git a/docs/usage/updating-rebasing.md b/docs/usage/updating-rebasing.md index 58d93f1e08fc75..562dfd56ebb742 100644 --- a/docs/usage/updating-rebasing.md +++ b/docs/usage/updating-rebasing.md @@ -12,6 +12,7 @@ Here is a list of the most common cases where Renovate must update/rebase the br - When a pull request has conflicts due to changes on the base branch - When you have enabled "Require branches to be up-to-date before merging" on GitHub - When you have manually told Renovate to rebase when behind the base branch with `"rebaseWhen": "behind-base-branch"` +- When you have set `keepUpdatedLabel` and included the label on a PR - When a newer version of the dependency is released - When you request a manual rebase from the Renovate bot - When you use `"automerge": true` and `"rebaseWhen": "auto"` on a branch / pr diff --git a/docs/usage/upgrade-best-practices.md b/docs/usage/upgrade-best-practices.md index 7852653ef9766d..02cda2732d8cad 100644 --- a/docs/usage/upgrade-best-practices.md +++ b/docs/usage/upgrade-best-practices.md @@ -80,7 +80,7 @@ The [GitHub Docs, using third-party actions](https://docs.github.com/en/actions/ We recommend pinning _all_ Actions. That's why the `helpers:pinGitHubActionDigests` preset pins all GitHub Actions. -For an in-depth explanation why you should pin your GitHub Actions, read the [Palo Alto Networks blogpost about the GitHub Actions worm](https://www.paloaltonetworks.com/blog/prisma-cloud/github-actions-worm-dependencies/). +For an in-depth explanation why you should pin your GitHub Actions, read the [Palo Alto Networks blog post about the GitHub Actions worm](https://www.paloaltonetworks.com/blog/prisma-cloud/github-actions-worm-dependencies/). #### Extends `:pinDevDependencies` diff --git a/docs/usage/user-stories/swissquote.md b/docs/usage/user-stories/swissquote.md index e7f187f6bd2e8e..8ffa13e58415bc 100644 --- a/docs/usage/user-stories/swissquote.md +++ b/docs/usage/user-stories/swissquote.md @@ -10,7 +10,7 @@ > This article was originally published on [Medium](https://medium.com/swissquote-engineering/how-swissquote-is-keeping-software-dependencies-up-to-date-with-renovate-6246e8b20437) by [Stéphane Goetz](https://onigoetz.ch/), Principal Software Engineer at [Swissquote Bank](https://github.com/swissquote/). Swissquote has more than 1000 distinct applications running in production. -They come in many different flavors including services, daemons, and webapps, and their age can be counted from days to more than a decade. +They come in many different flavors including services, daemons, and web apps, and their age can be counted from days to more than a decade. While there are many topics of interest when talking about software maintenance, today’s topic is software dependencies. We’ll see in this article why it’s important to keep them up-to-date and why it’s not as simple as one may think. @@ -182,7 +182,9 @@ Some features and options we enjoy: There is an [on-premise option](https://www.mend.io/free-developer-tools/renovate/on-premises/), but you can also use [the Mend Renovate App](https://github.com/marketplace/renovate). On our side, we’re not using the on-premise but rather a custom scheduler using the open source Docker image. -## Some stats after two years with Renovate +## Some stats after four years with Renovate + +> The figures here have been updated in November 2023 We started using Renovate Bot in 2019, using the (now deprecated) `renovate/pro` Docker image. We installed it as a GitHub app and some early adopters started to use it. @@ -197,18 +199,43 @@ Here is the dashboard for our current scheduler:
![Swissquote scheduler dashboard](../assets/images/swissquote_stats.png){ loading=lazy } -
A dashboard we made at Swissquote to keep our Renovate runs in check, July 2022.
+
A dashboard we made at Swissquote to keep our Renovate runs in check, November 2023.
We don’t force any team to use Renovate, each team can decide to opt-in and do it for each project separately. Some statistics: -- 824 repositories enabled out of about 2000 active repositories -- 8000 PRs were merged since we installed Renovate +- 857 repositories enabled out of about 2000 active repositories +- 11000 PRs were merged since we installed Renovate - 239 PRs were merged last month - 2 SSDs died on our Renovate machine with the number of projects to clone again and again +### How does the scheduler work? + +The scheduler is a Node.js application that handles an in-memory queue and starts Docker containers to run Renovate on. +Our custom scheduler application regularly sends data points to our InfluxDB database, which we then display in Grafana. + +Here is how it works: + +
+ ![Swissquote scheduler diagram](../assets/images/swissquote_stats_collection.png){ loading=lazy } +
A diagram explaining how our scheduler interacts with Renovate.
+
+ +All the information on the dashboard you saw above is created from three measurements: + +1. Queue: Every 5 minutes, we send the status of the queue size and the number of jobs currently running +1. Webhook: When receiving a webhook request from GitHub, we send a data point on the duration of treatment for that item +1. Runs: After each run, we send a data point on the run duration, success, and number of PRs created/updated/merged/closed + +The queue is filled by webhooks _or_ by re-queueing all repositories at regular intervals. +For each repository, we start a Renovate Docker image and pipe its logs to a file. +This allows us to run ten workers in parallel. +We could technically run more workers but decided not to hammer our GitHub instance. + +You can find more details in this [discussion on the Renovate repository, from November 2023](https://github.com/renovatebot/renovate/discussions/23105#discussioncomment-6366621). + ## The future of Renovate at Swissquote Not all teams are using Renovate at this stage, as some teams prefer to manually update their dependencies. diff --git a/jest.config.ts b/jest.config.ts index cf59e0deb91a89..9aa56f29293e1b 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,5 +1,6 @@ import crypto from 'node:crypto'; import os from 'node:os'; +import { env } from 'node:process'; import v8 from 'node:v8'; import { minimatch } from 'minimatch'; import type { JestConfigWithTsJest } from 'ts-jest'; @@ -205,11 +206,7 @@ const config: JestConfig = { '!lib/**/{__fixtures__,__mocks__,__testutil__,test}/**/*.{js,ts}', '!lib/**/types.ts', ], - coveragePathIgnorePatterns: [ - '/node_modules/', - '/test/', - '/tools/', - ], + coveragePathIgnorePatterns: getCoverageIgnorePatterns(), cacheDirectory: '.cache/jest', collectCoverage: true, coverageReporters: ci @@ -450,3 +447,12 @@ process.stderr.write(`Host stats: Memory: ${(mem / 1024 / 1024 / 1024).toFixed(2)} GB HeapLimit: ${(stats.heap_size_limit / 1024 / 1024 / 1024).toFixed(2)} GB `); +function getCoverageIgnorePatterns(): string[] | undefined { + const patterns = ['/node_modules/', '/test/', '/tools/']; + + if (env.TEST_LEGACY_DECRYPTION !== 'true') { + patterns.push('/lib/config/decrypt/legacy.ts'); + } + + return patterns; +} diff --git a/lib/config-validator.ts b/lib/config-validator.ts index 2a8aff79a338da..3c809bc2ea2e29 100644 --- a/lib/config-validator.ts +++ b/lib/config-validator.ts @@ -17,7 +17,7 @@ import { let returnVal = 0; async function validate( - isGlobalConfig: boolean, + configType: 'global' | 'repo', desc: string, config: RenovateConfig, strict: boolean, @@ -37,7 +37,7 @@ async function validate( } } const massagedConfig = massageConfig(migratedConfig); - const res = await validateConfig(isGlobalConfig, massagedConfig, isPreset); + const res = await validateConfig(configType, massagedConfig, isPreset); if (res.errors.length) { logger.error( { file: desc, errors: res.errors }, @@ -76,7 +76,7 @@ type PackageJson = { const parsedContent = await getParsedContent(file); try { logger.info(`Validating ${file}`); - await validate(true, file, parsedContent, strict); + await validate('global', file, parsedContent, strict); } catch (err) { logger.warn({ file, err }, 'File is not valid Renovate config'); returnVal = 1; @@ -97,7 +97,7 @@ type PackageJson = { const parsedContent = await getParsedContent(file); try { logger.info(`Validating ${file}`); - await validate(false, file, parsedContent, strict); + await validate('repo', file, parsedContent, strict); } catch (err) { logger.warn({ file, err }, 'File is not valid Renovate config'); returnVal = 1; @@ -114,7 +114,7 @@ type PackageJson = { if (pkgJson.renovate) { logger.info(`Validating package.json > renovate`); await validate( - false, + 'repo', 'package.json > renovate', pkgJson.renovate, strict, @@ -124,7 +124,7 @@ type PackageJson = { logger.info(`Validating package.json > renovate-config`); for (const presetConfig of Object.values(pkgJson['renovate-config'])) { await validate( - false, + 'repo', 'package.json > renovate-config', presetConfig, strict, @@ -141,7 +141,7 @@ type PackageJson = { const file = process.env.RENOVATE_CONFIG_FILE ?? 'config.js'; logger.info(`Validating ${file}`); try { - await validate(true, file, fileConfig, strict); + await validate('global', file, fileConfig, strict); } catch (err) { logger.error({ file, err }, 'File is not valid Renovate config'); returnVal = 1; diff --git a/lib/config/__snapshots__/decrypt.spec.ts.snap b/lib/config/__snapshots__/decrypt.spec.ts.snap deleted file mode 100644 index df59cc5018dd7c..00000000000000 --- a/lib/config/__snapshots__/decrypt.spec.ts.snap +++ /dev/null @@ -1,7 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`config/decrypt decryptConfig() appends npm token in npmrc 1`] = ` -"foo=bar -_authToken=abcdef-ghijklm-nopqf-stuvwxyz -" -`; diff --git a/lib/config/decrypt.spec.ts b/lib/config/decrypt.spec.ts index 30f3a83204b65b..8cde4a0378d6e3 100644 --- a/lib/config/decrypt.spec.ts +++ b/lib/config/decrypt.spec.ts @@ -1,11 +1,7 @@ -import { Fixtures } from '../../test/fixtures'; -import { CONFIG_VALIDATION } from '../constants/error-messages'; import { decryptConfig } from './decrypt'; import { GlobalConfig } from './global'; import type { RenovateConfig } from './types'; -const privateKey = Fixtures.get('private.pem'); -const privateKeyPgp = Fixtures.get('private-pgp.pem'); const repository = 'abc/def'; describe('config/decrypt', () => { @@ -29,184 +25,5 @@ describe('config/decrypt', () => { expect(res.encrypted).toBeUndefined(); expect(res.a).toBeUndefined(); }); - - it('handles invalid encrypted type', async () => { - config.encrypted = 1; - GlobalConfig.set({ privateKey }); - const res = await decryptConfig(config, repository); - expect(res.encrypted).toBeUndefined(); - }); - - it('handles invalid encrypted value', async () => { - config.encrypted = { a: 1 }; - GlobalConfig.set({ privateKey, privateKeyOld: 'invalid-key' }); - await expect(decryptConfig(config, repository)).rejects.toThrow( - CONFIG_VALIDATION, - ); - }); - - it('replaces npm token placeholder in npmrc', async () => { - GlobalConfig.set({ - privateKey: 'invalid-key', - privateKeyOld: privateKey, - }); // test old key failover - config.npmrc = - '//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n'; - config.encrypted = { - npmToken: - 'FLA9YHIzpE7YetAg/P0X46npGRCMqn7hgyzwX5ZQ9wYgu9BRRbTiBVsUIFTyM5BuP1Q22slT2GkWvFvum7GU236Y6QiT7Nr8SLvtsJn2XUuq8H7REFKzdy3+wqyyWbCErYTFyY1dcPM7Ht+CaGDWdd8u/FsoX7AdMRs/X1jNUo6iSmlUiyGlYDKF+QMnCJom1VPVgZXWsGKdjI2MLny991QMaiv0VajmFIh4ENv4CtXOl/1twvIl/6XTXAaqpJJKDTPZEuydi+PHDZmal2RAOfrkH4m0UURa7SlfpUlIg+EaqbNGp85hCYXLwRcEET1OnYr3rH1oYkcYJ40any1tvQ==', - }; - const res = await decryptConfig(config, repository); - expect(res.encrypted).toBeUndefined(); - expect(res.npmToken).toBeUndefined(); - expect(res.npmrc).toBe( - '//registry.npmjs.org/:_authToken=abcdef-ghijklm-nopqf-stuvwxyz\n//registry.npmjs.org/:_authToken=abcdef-ghijklm-nopqf-stuvwxyz\n', - ); - }); - - it('appends npm token in npmrc', async () => { - GlobalConfig.set({ privateKey }); - config.npmrc = 'foo=bar\n'; - config.encrypted = { - npmToken: - 'FLA9YHIzpE7YetAg/P0X46npGRCMqn7hgyzwX5ZQ9wYgu9BRRbTiBVsUIFTyM5BuP1Q22slT2GkWvFvum7GU236Y6QiT7Nr8SLvtsJn2XUuq8H7REFKzdy3+wqyyWbCErYTFyY1dcPM7Ht+CaGDWdd8u/FsoX7AdMRs/X1jNUo6iSmlUiyGlYDKF+QMnCJom1VPVgZXWsGKdjI2MLny991QMaiv0VajmFIh4ENv4CtXOl/1twvIl/6XTXAaqpJJKDTPZEuydi+PHDZmal2RAOfrkH4m0UURa7SlfpUlIg+EaqbNGp85hCYXLwRcEET1OnYr3rH1oYkcYJ40any1tvQ==', - }; - const res = await decryptConfig(config, repository); - expect(res.encrypted).toBeUndefined(); - expect(res.npmToken).toBeUndefined(); - expect(res.npmrc).toMatchSnapshot(); - }); - - it('decrypts nested', async () => { - GlobalConfig.set({ privateKey }); - config.packageFiles = [ - { - packageFile: 'package.json', - devDependencies: { - encrypted: { - branchPrefix: - 'FLA9YHIzpE7YetAg/P0X46npGRCMqn7hgyzwX5ZQ9wYgu9BRRbTiBVsUIFTyM5BuP1Q22slT2GkWvFvum7GU236Y6QiT7Nr8SLvtsJn2XUuq8H7REFKzdy3+wqyyWbCErYTFyY1dcPM7Ht+CaGDWdd8u/FsoX7AdMRs/X1jNUo6iSmlUiyGlYDKF+QMnCJom1VPVgZXWsGKdjI2MLny991QMaiv0VajmFIh4ENv4CtXOl/1twvIl/6XTXAaqpJJKDTPZEuydi+PHDZmal2RAOfrkH4m0UURa7SlfpUlIg+EaqbNGp85hCYXLwRcEET1OnYr3rH1oYkcYJ40any1tvQ==', - npmToken: - 'FLA9YHIzpE7YetAg/P0X46npGRCMqn7hgyzwX5ZQ9wYgu9BRRbTiBVsUIFTyM5BuP1Q22slT2GkWvFvum7GU236Y6QiT7Nr8SLvtsJn2XUuq8H7REFKzdy3+wqyyWbCErYTFyY1dcPM7Ht+CaGDWdd8u/FsoX7AdMRs/X1jNUo6iSmlUiyGlYDKF+QMnCJom1VPVgZXWsGKdjI2MLny991QMaiv0VajmFIh4ENv4CtXOl/1twvIl/6XTXAaqpJJKDTPZEuydi+PHDZmal2RAOfrkH4m0UURa7SlfpUlIg+EaqbNGp85hCYXLwRcEET1OnYr3rH1oYkcYJ40any1tvQ==', - }, - }, - }, - 'backend/package.json', - ]; - // TODO: fix types #22198 - const res = (await decryptConfig(config, repository)) as any; - expect(res.encrypted).toBeUndefined(); - expect(res.packageFiles[0].devDependencies.encrypted).toBeUndefined(); - expect(res.packageFiles[0].devDependencies.branchPrefix).toBe( - 'abcdef-ghijklm-nopqf-stuvwxyz', - ); - expect(res.packageFiles[0].devDependencies.npmToken).toBeUndefined(); - expect(res.packageFiles[0].devDependencies.npmrc).toBe( - '//registry.npmjs.org/:_authToken=abcdef-ghijklm-nopqf-stuvwxyz\n', - ); - }); - - it('rejects invalid PGP message', async () => { - GlobalConfig.set({ privateKey: privateKeyPgp }); - config.encrypted = { - token: - 'long-but-wrong-wcFMAw+4H7SgaqGOAQ//ZNPgHJ4RQBdfFoDX8Ywe9UxqMlc8k6VasCszQ2JULh/BpEdKdgRUGNaKaeZ+oBKYDBmDwAD5V5FEMlsg+KO2gykp/p2BAwvKGtYK0MtxLh4h9yJbN7TrVnGO3/cC+Inp8exQt0gD6f1Qo/9yQ9NE4/BIbaSs2b2DgeIK7Ed8N675AuSo73UOa6o7t+9pKeAAK5TQwgSvolihbUs8zjnScrLZD+nhvL3y5gpAqK9y//a+bTu6xPA1jdLjsswoCUq/lfVeVsB2GWV2h6eex/0fRKgN7xxNgdMn0a7msrvumhTawP8mPisPY2AAsHRIgQ9vdU5HbOPdGoIwI9n9rMdIRn9Dy7/gcX9Ic+RP2WwS/KnPHLu/CveY4W5bYqYoikWtJs9HsBCyWFiHIRrJF+FnXwtKdoptRfxTfJIkBoLrV6fDIyKo79iL+xxzgrzWs77KEJUJfexZBEGBCnrV2o7mo3SU197S0qx7HNvqrmeCj8CLxq8opXC71TNa+XE6BQUVyhMFxtW9LNxZUHRiNzrTSikArT4hzjyr3f9cb0kZVcs6XJQsm1EskU3WXo7ETD7nsukS9GfbwMn7tfYidB/yHSHl09ih871BcgByDmEKKdmamcNilW2bmTAqB5JmtaYT5/H8jRQWo/VGrEqlmiA4KmwSv7SZPlDnaDFrmzmMZZDSRgHe5KWl283XLmSeE8J0NPqwFH3PeOv4fIbOjJrnbnFBwSAsgsMe2K4OyFDh2COfrho7s8EP1Kl5lBkYJ+VRreGRerdSu24', - }; - await expect(decryptConfig(config, repository)).rejects.toThrow( - CONFIG_VALIDATION, - ); - config.encrypted = { - // Missing value - token: - 'wcFMAw+4H7SgaqGOAQ//ZNPgHJ4RQBdfFoDX8Ywe9UxqMlc8k6VasCszQ2JULh/BpEdKdgRUGNaKaeZ+oBKYDBmDwAD5V5FEMlsg+KO2gykp/p2BAwvKGtYK0MtxLh4h9yJbN7TrVnGO3/cC+Inp8exQt0gD6f1Qo/9yQ9NE4/BIbaSs2b2DgeIK7Ed8N675AuSo73UOa6o7t+9pKeAAK5TQwgSvolihbUs8zjnScrLZD+nhvL3y5gpAqK9y//a+bTu6xPA1jdLjsswoCUq/lfVeVsB2GWV2h6eex/0fRKgN7xxNgdMn0a7msrvumhTawP8mPisPY2AAsHRIgQ9vdU5HbOPdGoIwI9n9rMdIRn9Dy7/gcX9Ic+RP2WwS/KnPHLu/CveY4W5bYqYoikWtJs9HsBCyWFiHIRrJF+FnXwtKdoptRfxTfJIkBoLrV6fDIyKo79iL+xxzgrzWs77KEJUJfexZBEGBCnrV2o7mo3SU197S0qx7HNvqrmeCj8CLxq8opXC71TNa+XE6BQUVyhMFxtW9LNxZUHRiNzrTSikArT4hzjyr3f9cb0kZVcs6XJQsm1EskU3WXo7ETD7nsukS9GfbwMn7tfYidB/yHSHl09ih871BcgByDmEKKdmamcNilW2bmTAqB5JmtaYT5/H8jRQWo/VGrEqlmiA4KmwSv7SZPlDnaDFrmzmMZZDSRgHe5KWl283XLmSeE8J0NPqwFH3PeOv4fIbOjJrnbnFBwSAsgsMe2K4OyFDh2COfrho7s8EP1Kl5lBkYJ+VRreGRerdSu24', - }; - await expect(decryptConfig(config, repository)).rejects.toThrow( - CONFIG_VALIDATION, - ); - config.encrypted = { - // Missing org scope - token: - 'wcFMAw+4H7SgaqGOAQ//W38A3PmaZnE9XTCHGDQFD52Kz78UYnaiYeAT13cEqYWTwEvQ57B7D7I6i4jCLe7KwkUCS90kyoqd7twD75W/sO70MyIveKnMlqqnpkagQkFgmzMaXXNHaJXEkjzsflTELZu6UsUs/kZYmab7r14YLl9HbH/pqN9exil/9s3ym9URCPOyw/l04KWntdMAy0D+c5M4mE+obv6fz6nDb8tkdeT5Rt2uU+qw3gH1OsB2yu+zTWpI/xTGwDt5nB5txnNTsVrQ/ZK85MSktacGVcYuU9hsEDmSrShmtqlg6Myq+Hjb7cYAp2g4n13C/I3gGGaczl0PZaHD7ALMjI7p6O1q+Ix7vMxipiKMVjS3omJoqBCz3FKc6DVhyX4tfhxgLxFo0DpixNwGbBRbMBO8qZfUk7bicAl/oCRc2Ijmay5DDYuvtkw3G3Ou+sZTe6DNpWUFy6VA4ai7hhcLvcAuiYmLdwPISRR/X4ePa8ZrmSVPyVOvbmmwLhcDYSDlC9Mw4++7ELomlve5kvjVSHvPv9BPVb5sJF7gX4vOT4FrcKalQRPmhNCZrE8tY2lvlrXwV2EEhya8EYv4QTd3JUYEYW5FXiJrORK5KDTnISw+U02nFZjFlnoz9+R6h+aIT1crS3/+YjCHE/EIKvSftOnieYb02Gk7M9nqU19EYL9ApYw4+IjSRgFM3DShIrvuDwDkAwUfaq8mKtr9Vjg/r+yox//GKS3u3r4I3+dfCljA3OwskTPfbSD+huBk4mylIvaL5v8Fngxo979wiLw', - }; - await expect(decryptConfig(config, repository)).rejects.toThrow( - CONFIG_VALIDATION, - ); - config.encrypted = { - // Impossible to parse - token: - 'wcFMAw+4H7SgaqGOAQ//Wa/gHgQdH7tj3LQdW6rWKjzmkYVKZW9EbexJExu4WLaMgEKodlRMilcqCKfQZpjzoiC31J8Ly/x6Soury+lQnLVbtIQ4KWa/uCIz4lXCpPpGNgN2jPfOmdwWBMOcXIT+BgAMxRu3rAmvTtunrkACJ3J92eYNwJhTzp2Azn9LpT7kHnZ64z2SPhbdUgMMhCBwBG5BPArPzF5fdaqa8uUSbKhY0GMiqPXq6Zeq+EBNoPc/RJp2urpYTknO+nRb39avKjihd9MCZ/1d3QYymbRj7SZC3LJhenVF0hil3Uk8TBASnGQiDmBcIXQFhJ0cxavXqKjx+AEALq+kTdwGu5vuE2+2B820/o3lAXR9OnJHr8GodJ2ZBpzOaPrQe5zvxL0gLEeUUPatSOwuLhdo/6+bRCl2wNz23jIjDEFFTmsLqfEHcdVYVTH2QqvLjnUYcCRRuM32vS4rCMOEe0l6p0CV2rk22UZDIPcxqXjKucxse2Sow8ATWiPoIw7zWj7XBLqUKHFnMpPV2dCIKFKBsOKYgLjF4BvKzZJyhmVEPgMcKQLYqeT/2uWDR77NSWH0Cyiwk9M3KbOIMmV3pWh9PiXk6CvumECELbJHYH0Mc+P//BnbDq2Ie9dHdmKhFgRyHU7gWvkPhic9BX36xyldPcnhTgr1XWRoVe0ETGLDPCcqrQ/SUQGrLiujSOgxGu2K/6LDJhi4IKz1/nf7FUSj5eTIDqQiSPP5pXDjlH7oYxXXrHI/aYOCZ5sBx7mOzlEcENIrYblCHO/CYMTWdCJ4Wrftqk7K/A=', - }; - await expect(decryptConfig(config, repository)).rejects.toThrow( - CONFIG_VALIDATION, - ); - config.encrypted = { - token: 'too-short', - }; - await expect(decryptConfig(config, repository)).rejects.toThrow( - CONFIG_VALIDATION, - ); - }); - - it('handles PGP org constraint', async () => { - GlobalConfig.set({ privateKey: privateKeyPgp }); - config.encrypted = { - token: - 'wcFMAw+4H7SgaqGOAQ/+Lz6RlbEymbnmMhrktuaGiDPWRNPEQFuMRwwYM6/B/r0JMZa9tskAA5RpyYKxGmJJeuRtlA8GkTw02GoZomlJf/KXJZ95FwSbkXMSRJRD8LJ2402Hw2TaOTaSvfamESnm8zhNo8cok627nkKQkyrpk64heVlU5LIbO2+UgYgbiSQjuXZiW+QuJ1hVRjx011FQgEYc59+22yuKYqd8rrni7TrVqhGRlHCAqvNAGjBI4H7uTFh0sP4auunT/JjxTeTkJoNu8KgS/LdrvISpO67TkQziZo9XD5FOzSN7N3e4f8vO4N4fpjgkIDH/9wyEYe0zYz34xMAFlnhZzqrHycRqzBJuMxGqlFQcKWp9IisLMoVJhLrnvbDLuwwcjeqYkhvODjSs7UDKwTE4X4WmvZr0x4kOclOeAAz/pM6oNVnjgWJd9SnYtoa67bZVkne0k6mYjVhosie8v8icijmJ4OyLZUGWnjZCRd/TPkzQUw+B0yvsop9FYGidhCI+4MVx6W5w7SRtCctxVfCjLpmU4kWaBUUJ5YIQ5xm55yxEYuAsQkxOAYDCMFlV8ntWStYwIG1FsBgJX6VPevXuPPMjWiPNedIpJwBH2PLB4blxMfzDYuCeaIqU4daDaEWxxpuFTTK9fLdJKuipwFG6rwE3OuijeSN+2SLszi834DXtUjQdikHSTQG392+oTmZCFPeffLk/OiV2VpdXF3gGL7sr5M9hOWIZ783q0vW1l6nAElZ7UA//kW+L6QRxbnBVTJK5eCmMY6RJmL76zjqC1jQ0FC10', - }; - const res = await decryptConfig(config, repository); - expect(res.encrypted).toBeUndefined(); - expect(res.token).toBe('123'); - await expect(decryptConfig(config, 'wrong/org')).rejects.toThrow( - CONFIG_VALIDATION, - ); - }); - - it('handles PGP multi-org constraint', async () => { - GlobalConfig.set({ privateKey: privateKeyPgp }); - config.encrypted = { - token: - 'wcFMAw+4H7SgaqGOAQ//Yk4RTQoLEhO0TKxN2IUBrCi88ts+CG1SXKeL06sJ2qikN/3n2JYAGGKgkHRICfu5dOnsjyFdLJ1XWUrbsM3XgVWikMbrmzD1Xe7N5DsoZXlt4Wa9pZ+IkZuE6XcKKu9whIJ22ciEwCzFwDmk/CBshdCCVVQ3IYuM6uibEHn/AHQ8K15XhraiSzF6DbJpevs5Cy7b5YHFyE936H25CVnouUQnMPsirpQq3pYeMq/oOtV/m4mfRUUQ7MUxvtrwE4lq4hLjFu5n9rwlcqaFPl7I7BEM++1c9LFpYsP5mTS7hHCZ9wXBqER8fa3fKYx0bK1ihCpjP4zUkR7P/uhWDArXamv7gHX2Kj/Qsbegn7KjTdZlggAmaJl/CuSgCbhySy+E55g3Z1QFajiLRpQ5+RsWFDbbI08YEgzyQ0yNCaRvrkgo7kZ1D95rEGRfY96duOQbjzOEqtvYmFChdemZ2+f9Kh/JH1+X9ynxY/zYe/0p/U7WD3QNTYN18loc4aXiB1adXD5Ka2QfNroLudQBmLaJpJB6wASFfuxddsD5yRnO32NSdRaqIWC1x6ti3ZYJZ2RsNwJExPDzjpQTuMOH2jtpu3q7NHmW3snRKy2YAL2UjI0YdeKIlhc/qLCJt9MRcOxWYvujTMD/yGprhG44qf0jjMkJBu7NjuVIMONujabl9b7SUQGfO/t+3rMuC68bQdCGLlO8gf3hvtD99utzXphi6idjC0HKSW/9KzuMkm+syGmIAYq/0L3EFvpZ38uq7z8KzwFFQHI3sBA34bNEr5zpU5OMWg', - }; - let res = await decryptConfig(config, repository); - expect(res.encrypted).toBeUndefined(); - expect(res.token).toBe('123'); - res = await decryptConfig(config, 'def/ghi'); - expect(res.encrypted).toBeUndefined(); - expect(res.token).toBe('123'); - await expect(decryptConfig(config, 'wrong/org')).rejects.toThrow( - CONFIG_VALIDATION, - ); - }); - - it('handles PGP org/repo constraint', async () => { - GlobalConfig.set({ privateKey: privateKeyPgp }); - config.encrypted = { - token: - 'wcFMAw+4H7SgaqGOAQ//Wp7N0PaDZp0uOdwsc1CuqAq0UPcq+IQdHyKpJs3tHiCecXBHogy4P+rY9nGaUrVneCr4HexuKGuyJf1yl0ZqFffAUac5PjF8eDvjukQGOUq4aBlOogJCEefnuuVxVJx+NRR5iF1P6v57bmI1c+zoqZI/EQB30KU6O1BsdGPLUA/+R3dwCZd5Mbd36s34eYBasqcY9/QbqFcpElXMEPMse3kMCsVXPbZ+UMjtPJiBPUmtJq+ifnu1LzDrfshusSQMwgd/QNk7nEsijiYKllkWhHTP6g7zigvJ46x0h6AYS108YiuK3B9XUhXN9m05Ac6KTEEUdRI3E/dK2dQuRkLjXC8wceQm4A19Gm0uHoMIJYOCbiVoBCH6ayvKbZWZV5lZ4D1JbDNGmKeIj6OX9XWEMKiwTx0Xe89V7BdJzwIGrL0TCLtXuYWZ/R2k+UuBqtgzr44BsBqMpKUA0pcGBoqsEou1M05Ae9fJMF6ADezF5UQZPxT1hrMldiTp3p9iHGfWN2tKHeoW/8CqlIqg9JEkTc+Pl/L9E6ndy5Zjf097PvcmSGhxUQBE7XlrZoIlGhiEU/1HPMen0UUIs0LUu1ywpjCex2yTWnU2YmEwy0MQI1sekSr96QFxDDz9JcynYOYbqR/X9pdxEWyzQ+NJ3n6K97nE1Dj9Sgwu7mFGiUdNkf/SUAF0eZi/eXg71qumpMGBd4eWPtgkeMPLHjvMSYw9vBUfcoKFz6RJ4woG0dw5HOFkPnIjXKWllnl/o01EoBp/o8uswsIS9Nb8i+bp27U6tAHE', - }; - const res = await decryptConfig(config, repository); - expect(res.encrypted).toBeUndefined(); - expect(res.token).toBe('123'); - await expect(decryptConfig(config, 'abc/defg')).rejects.toThrow( - CONFIG_VALIDATION, - ); - }); - - it('handles PGP multi-org/repo constraint', async () => { - GlobalConfig.set({ privateKey: privateKeyPgp }); - config.encrypted = { - token: - 'wcFMAw+4H7SgaqGOARAAibXL3zr0KZawiND868UGdPpGRo1aVZfn0NUBHpm8mXfgB1rBHaLsP7qa8vxDHpwH9DRD1IyB4vvPUwtu7wmuv1Vtr596tD40CCcCZYB5JjZLWRF0O0xaZFCOi7Z9SqqdaOQoMScyvPO+3/lJkS7zmLllJFH0mQoX5Cr+owUAMSWqbeCQ9r/KAXpnhmpraDjTav48WulcdTMc8iQ/DHimcdzHErLOAjtiQi4OUe1GnDCcN76KQ+c+ZHySnkXrYi/DhOOu9qB4glJ5n68NueFja+8iR39z/wqCI6V6TIUiOyjFN86iVyNPQ4Otem3KuNwrnwSABLDqP491eUNjT8DUDffsyhNC9lnjQLmtViK0EN2yLVpMdHq9cq8lszBChB7gobD9rm8nUHnTuLf6yJvZOj6toD5Yqj8Ibj58wN90Q8CUsBp9/qp0J+hBVUPOx4sT6kM2p6YarlgX3mrIW5c1U+q1eDbCddLjHiU5cW7ja7o+cqlA6mbDRu3HthjBweiXTicXZcRu1o/wy/+laQQ95x5FzAXDnOwQUHBmpTDI3tUJvQ+oy8XyBBbyC0LsBye2c2SLkPJ4Ai3IMR+Mh8puSzVywTbneiAQNBzJHlj5l85nCF2tUjvNo3dWC+9mU5sfXg11iEC6LRbg+icjpqRtTjmQURtciKDUbibWacwU5T/SVAGPXnW7adBOS0PZPIZQcSwjchOdOl0IjzBy6ofu7ODdn2CXZXi8zbevTICXsHvjnW4MAj5oXrStxK3LkWyM3YBOLe7sOfWvWz7n9TM3dHg032navQ', - }; - let res = await decryptConfig(config, repository); - expect(res.encrypted).toBeUndefined(); - expect(res.token).toBe('123'); - res = await decryptConfig(config, 'def/def'); - expect(res.encrypted).toBeUndefined(); - expect(res.token).toBe('123'); - await expect(decryptConfig(config, 'abc/defg')).rejects.toThrow( - CONFIG_VALIDATION, - ); - }); }); }); diff --git a/lib/config/decrypt.ts b/lib/config/decrypt.ts index 4ea8ddcd0ac004..1895b6dc8ff698 100644 --- a/lib/config/decrypt.ts +++ b/lib/config/decrypt.ts @@ -1,104 +1,50 @@ -import crypto from 'node:crypto'; import is from '@sindresorhus/is'; -import * as openpgp from 'openpgp'; import { logger } from '../logger'; import { maskToken } from '../util/mask'; import { regEx } from '../util/regex'; import { addSecretForSanitizing } from '../util/sanitize'; import { ensureTrailingSlash } from '../util/url'; +import { tryDecryptKbPgp } from './decrypt/kbpgp'; +import { + tryDecryptPublicKeyDefault, + tryDecryptPublicKeyPKCS1, +} from './decrypt/legacy'; +import { tryDecryptOpenPgp } from './decrypt/openpgp'; import { GlobalConfig } from './global'; import { DecryptedObject } from './schema'; import type { RenovateConfig } from './types'; -export async function tryDecryptPgp( - privateKey: string, - encryptedStr: string, -): Promise { - if (encryptedStr.length < 500) { - // optimization during transition of public key -> pgp - return null; - } - try { - const pk = await openpgp.readPrivateKey({ - // prettier-ignore - armoredKey: privateKey.replace(regEx(/\n[ \t]+/g), '\n'), // little massage to help a common problem - }); - const startBlock = '-----BEGIN PGP MESSAGE-----\n\n'; - const endBlock = '\n-----END PGP MESSAGE-----'; - let armoredMessage = encryptedStr.trim(); - if (!armoredMessage.startsWith(startBlock)) { - armoredMessage = `${startBlock}${armoredMessage}`; - } - if (!armoredMessage.endsWith(endBlock)) { - armoredMessage = `${armoredMessage}${endBlock}`; - } - const message = await openpgp.readMessage({ - armoredMessage, - }); - const { data } = await openpgp.decrypt({ - message, - decryptionKeys: pk, - }); - logger.debug('Decrypted config using openpgp'); - return data; - } catch (err) { - logger.debug({ err }, 'Could not decrypt using openpgp'); - return null; - } -} - -export function tryDecryptPublicKeyDefault( - privateKey: string, - encryptedStr: string, -): string | null { - let decryptedStr: string | null = null; - try { - decryptedStr = crypto - .privateDecrypt(privateKey, Buffer.from(encryptedStr, 'base64')) - .toString(); - logger.debug('Decrypted config using default padding'); - } catch (err) { - logger.debug('Could not decrypt using default padding'); - } - return decryptedStr; -} - -export function tryDecryptPublicKeyPKCS1( - privateKey: string, - encryptedStr: string, -): string | null { - let decryptedStr: string | null = null; - try { - decryptedStr = crypto - .privateDecrypt( - { - key: privateKey, - padding: crypto.constants.RSA_PKCS1_PADDING, - }, - Buffer.from(encryptedStr, 'base64'), - ) - .toString(); - } catch (err) { - logger.debug('Could not decrypt using PKCS1 padding'); - } - return decryptedStr; -} - export async function tryDecrypt( privateKey: string, encryptedStr: string, repository: string, + keyName: string, ): Promise { let decryptedStr: string | null = null; if (privateKey?.startsWith('-----BEGIN PGP PRIVATE KEY BLOCK-----')) { - const decryptedObjStr = await tryDecryptPgp(privateKey, encryptedStr); + const decryptedObjStr = + process.env.RENOVATE_X_USE_OPENPGP === 'true' + ? await tryDecryptOpenPgp(privateKey, encryptedStr) + : await tryDecryptKbPgp(privateKey, encryptedStr); if (decryptedObjStr) { decryptedStr = validateDecryptedValue(decryptedObjStr, repository); } } else { decryptedStr = tryDecryptPublicKeyDefault(privateKey, encryptedStr); - if (!is.string(decryptedStr)) { + if (is.string(decryptedStr)) { + logger.warn( + { keyName }, + 'Encrypted value is using deprecated default padding, please change to using PGP encryption.', + ); + } else { decryptedStr = tryDecryptPublicKeyPKCS1(privateKey, encryptedStr); + // istanbul ignore if + if (is.string(decryptedStr)) { + logger.warn( + { keyName }, + 'Encrypted value is using deprecated PKCS1 padding, please change to using PGP encryption.', + ); + } } } return decryptedStr; @@ -191,10 +137,20 @@ export async function decryptConfig( if (privateKey) { for (const [eKey, eVal] of Object.entries(val)) { logger.debug('Trying to decrypt ' + eKey); - let decryptedStr = await tryDecrypt(privateKey, eVal, repository); + let decryptedStr = await tryDecrypt( + privateKey, + eVal, + repository, + eKey, + ); if (privateKeyOld && !is.nonEmptyString(decryptedStr)) { logger.debug(`Trying to decrypt with old private key`); - decryptedStr = await tryDecrypt(privateKeyOld, eVal, repository); + decryptedStr = await tryDecrypt( + privateKeyOld, + eVal, + repository, + eKey, + ); } if (!is.nonEmptyString(decryptedStr)) { const error = new Error('config-validation'); diff --git a/lib/config/decrypt/kbpgp.spec.ts b/lib/config/decrypt/kbpgp.spec.ts new file mode 100644 index 00000000000000..008ba46fa2ce08 --- /dev/null +++ b/lib/config/decrypt/kbpgp.spec.ts @@ -0,0 +1,132 @@ +import { Fixtures } from '../../../test/fixtures'; +import { CONFIG_VALIDATION } from '../../constants/error-messages'; +import { decryptConfig } from '../decrypt'; +import { GlobalConfig } from '../global'; +import type { RenovateConfig } from '../types'; +import { tryDecryptKbPgp } from './kbpgp'; + +const privateKey = Fixtures.get('private-pgp.pem', '..'); +const repository = 'abc/def'; + +describe('config/decrypt/kbpgp', () => { + describe('decryptConfig()', () => { + let config: RenovateConfig; + + beforeEach(() => { + config = {}; + GlobalConfig.reset(); + }); + + it('returns null for invalid key', async () => { + expect( + await tryDecryptKbPgp( + 'invalid-key', + 'wcFMAw+4H7SgaqGOAQ/+Lz6RlbEymbnmMhrktuaGiDPWRNPEQFuMRwwYM6/B/r0JMZa9tskAA5RpyYKxGmJJeuRtlA8GkTw02GoZomlJf/KXJZ95FwSbkXMSRJRD8LJ2402Hw2TaOTaSvfamESnm8zhNo8cok627nkKQkyrpk64heVlU5LIbO2+UgYgbiSQjuXZiW+QuJ1hVRjx011FQgEYc59+22yuKYqd8rrni7TrVqhGRlHCAqvNAGjBI4H7uTFh0sP4auunT/JjxTeTkJoNu8KgS/LdrvISpO67TkQziZo9XD5FOzSN7N3e4f8vO4N4fpjgkIDH/9wyEYe0zYz34xMAFlnhZzqrHycRqzBJuMxGqlFQcKWp9IisLMoVJhLrnvbDLuwwcjeqYkhvODjSs7UDKwTE4X4WmvZr0x4kOclOeAAz/pM6oNVnjgWJd9SnYtoa67bZVkne0k6mYjVhosie8v8icijmJ4OyLZUGWnjZCRd/TPkzQUw+B0yvsop9FYGidhCI+4MVx6W5w7SRtCctxVfCjLpmU4kWaBUUJ5YIQ5xm55yxEYuAsQkxOAYDCMFlV8ntWStYwIG1FsBgJX6VPevXuPPMjWiPNedIpJwBH2PLB4blxMfzDYuCeaIqU4daDaEWxxpuFTTK9fLdJKuipwFG6rwE3OuijeSN+2SLszi834DXtUjQdikHSTQG392+oTmZCFPeffLk/OiV2VpdXF3gGL7sr5M9hOWIZ783q0vW1l6nAElZ7UA//kW+L6QRxbnBVTJK5eCmMY6RJmL76zjqC1jQ0FC10', + ), + ).toBeNull(); + }); + + it('rejects invalid PGP message', async () => { + GlobalConfig.set({ privateKey }); + config.encrypted = { + token: + 'long-but-wrong-wcFMAw+4H7SgaqGOAQ//ZNPgHJ4RQBdfFoDX8Ywe9UxqMlc8k6VasCszQ2JULh/BpEdKdgRUGNaKaeZ+oBKYDBmDwAD5V5FEMlsg+KO2gykp/p2BAwvKGtYK0MtxLh4h9yJbN7TrVnGO3/cC+Inp8exQt0gD6f1Qo/9yQ9NE4/BIbaSs2b2DgeIK7Ed8N675AuSo73UOa6o7t+9pKeAAK5TQwgSvolihbUs8zjnScrLZD+nhvL3y5gpAqK9y//a+bTu6xPA1jdLjsswoCUq/lfVeVsB2GWV2h6eex/0fRKgN7xxNgdMn0a7msrvumhTawP8mPisPY2AAsHRIgQ9vdU5HbOPdGoIwI9n9rMdIRn9Dy7/gcX9Ic+RP2WwS/KnPHLu/CveY4W5bYqYoikWtJs9HsBCyWFiHIRrJF+FnXwtKdoptRfxTfJIkBoLrV6fDIyKo79iL+xxzgrzWs77KEJUJfexZBEGBCnrV2o7mo3SU197S0qx7HNvqrmeCj8CLxq8opXC71TNa+XE6BQUVyhMFxtW9LNxZUHRiNzrTSikArT4hzjyr3f9cb0kZVcs6XJQsm1EskU3WXo7ETD7nsukS9GfbwMn7tfYidB/yHSHl09ih871BcgByDmEKKdmamcNilW2bmTAqB5JmtaYT5/H8jRQWo/VGrEqlmiA4KmwSv7SZPlDnaDFrmzmMZZDSRgHe5KWl283XLmSeE8J0NPqwFH3PeOv4fIbOjJrnbnFBwSAsgsMe2K4OyFDh2COfrho7s8EP1Kl5lBkYJ+VRreGRerdSu24', + }; + await expect(decryptConfig(config, repository)).rejects.toThrow( + CONFIG_VALIDATION, + ); + config.encrypted = { + // Missing value + token: + 'wcFMAw+4H7SgaqGOAQ//ZNPgHJ4RQBdfFoDX8Ywe9UxqMlc8k6VasCszQ2JULh/BpEdKdgRUGNaKaeZ+oBKYDBmDwAD5V5FEMlsg+KO2gykp/p2BAwvKGtYK0MtxLh4h9yJbN7TrVnGO3/cC+Inp8exQt0gD6f1Qo/9yQ9NE4/BIbaSs2b2DgeIK7Ed8N675AuSo73UOa6o7t+9pKeAAK5TQwgSvolihbUs8zjnScrLZD+nhvL3y5gpAqK9y//a+bTu6xPA1jdLjsswoCUq/lfVeVsB2GWV2h6eex/0fRKgN7xxNgdMn0a7msrvumhTawP8mPisPY2AAsHRIgQ9vdU5HbOPdGoIwI9n9rMdIRn9Dy7/gcX9Ic+RP2WwS/KnPHLu/CveY4W5bYqYoikWtJs9HsBCyWFiHIRrJF+FnXwtKdoptRfxTfJIkBoLrV6fDIyKo79iL+xxzgrzWs77KEJUJfexZBEGBCnrV2o7mo3SU197S0qx7HNvqrmeCj8CLxq8opXC71TNa+XE6BQUVyhMFxtW9LNxZUHRiNzrTSikArT4hzjyr3f9cb0kZVcs6XJQsm1EskU3WXo7ETD7nsukS9GfbwMn7tfYidB/yHSHl09ih871BcgByDmEKKdmamcNilW2bmTAqB5JmtaYT5/H8jRQWo/VGrEqlmiA4KmwSv7SZPlDnaDFrmzmMZZDSRgHe5KWl283XLmSeE8J0NPqwFH3PeOv4fIbOjJrnbnFBwSAsgsMe2K4OyFDh2COfrho7s8EP1Kl5lBkYJ+VRreGRerdSu24', + }; + await expect(decryptConfig(config, repository)).rejects.toThrow( + CONFIG_VALIDATION, + ); + config.encrypted = { + // Missing org scope + token: + 'wcFMAw+4H7SgaqGOAQ//W38A3PmaZnE9XTCHGDQFD52Kz78UYnaiYeAT13cEqYWTwEvQ57B7D7I6i4jCLe7KwkUCS90kyoqd7twD75W/sO70MyIveKnMlqqnpkagQkFgmzMaXXNHaJXEkjzsflTELZu6UsUs/kZYmab7r14YLl9HbH/pqN9exil/9s3ym9URCPOyw/l04KWntdMAy0D+c5M4mE+obv6fz6nDb8tkdeT5Rt2uU+qw3gH1OsB2yu+zTWpI/xTGwDt5nB5txnNTsVrQ/ZK85MSktacGVcYuU9hsEDmSrShmtqlg6Myq+Hjb7cYAp2g4n13C/I3gGGaczl0PZaHD7ALMjI7p6O1q+Ix7vMxipiKMVjS3omJoqBCz3FKc6DVhyX4tfhxgLxFo0DpixNwGbBRbMBO8qZfUk7bicAl/oCRc2Ijmay5DDYuvtkw3G3Ou+sZTe6DNpWUFy6VA4ai7hhcLvcAuiYmLdwPISRR/X4ePa8ZrmSVPyVOvbmmwLhcDYSDlC9Mw4++7ELomlve5kvjVSHvPv9BPVb5sJF7gX4vOT4FrcKalQRPmhNCZrE8tY2lvlrXwV2EEhya8EYv4QTd3JUYEYW5FXiJrORK5KDTnISw+U02nFZjFlnoz9+R6h+aIT1crS3/+YjCHE/EIKvSftOnieYb02Gk7M9nqU19EYL9ApYw4+IjSRgFM3DShIrvuDwDkAwUfaq8mKtr9Vjg/r+yox//GKS3u3r4I3+dfCljA3OwskTPfbSD+huBk4mylIvaL5v8Fngxo979wiLw', + }; + await expect(decryptConfig(config, repository)).rejects.toThrow( + CONFIG_VALIDATION, + ); + config.encrypted = { + // Impossible to parse + token: + 'wcFMAw+4H7SgaqGOAQ//Wa/gHgQdH7tj3LQdW6rWKjzmkYVKZW9EbexJExu4WLaMgEKodlRMilcqCKfQZpjzoiC31J8Ly/x6Soury+lQnLVbtIQ4KWa/uCIz4lXCpPpGNgN2jPfOmdwWBMOcXIT+BgAMxRu3rAmvTtunrkACJ3J92eYNwJhTzp2Azn9LpT7kHnZ64z2SPhbdUgMMhCBwBG5BPArPzF5fdaqa8uUSbKhY0GMiqPXq6Zeq+EBNoPc/RJp2urpYTknO+nRb39avKjihd9MCZ/1d3QYymbRj7SZC3LJhenVF0hil3Uk8TBASnGQiDmBcIXQFhJ0cxavXqKjx+AEALq+kTdwGu5vuE2+2B820/o3lAXR9OnJHr8GodJ2ZBpzOaPrQe5zvxL0gLEeUUPatSOwuLhdo/6+bRCl2wNz23jIjDEFFTmsLqfEHcdVYVTH2QqvLjnUYcCRRuM32vS4rCMOEe0l6p0CV2rk22UZDIPcxqXjKucxse2Sow8ATWiPoIw7zWj7XBLqUKHFnMpPV2dCIKFKBsOKYgLjF4BvKzZJyhmVEPgMcKQLYqeT/2uWDR77NSWH0Cyiwk9M3KbOIMmV3pWh9PiXk6CvumECELbJHYH0Mc+P//BnbDq2Ie9dHdmKhFgRyHU7gWvkPhic9BX36xyldPcnhTgr1XWRoVe0ETGLDPCcqrQ/SUQGrLiujSOgxGu2K/6LDJhi4IKz1/nf7FUSj5eTIDqQiSPP5pXDjlH7oYxXXrHI/aYOCZ5sBx7mOzlEcENIrYblCHO/CYMTWdCJ4Wrftqk7K/A=', + }; + await expect(decryptConfig(config, repository)).rejects.toThrow( + CONFIG_VALIDATION, + ); + config.encrypted = { + token: 'too-short', + }; + await expect(decryptConfig(config, repository)).rejects.toThrow( + CONFIG_VALIDATION, + ); + }); + + it('handles PGP org constraint', async () => { + GlobalConfig.set({ privateKey }); + config.encrypted = { + token: + 'wcFMAw+4H7SgaqGOAQ/+Lz6RlbEymbnmMhrktuaGiDPWRNPEQFuMRwwYM6/B/r0JMZa9tskAA5RpyYKxGmJJeuRtlA8GkTw02GoZomlJf/KXJZ95FwSbkXMSRJRD8LJ2402Hw2TaOTaSvfamESnm8zhNo8cok627nkKQkyrpk64heVlU5LIbO2+UgYgbiSQjuXZiW+QuJ1hVRjx011FQgEYc59+22yuKYqd8rrni7TrVqhGRlHCAqvNAGjBI4H7uTFh0sP4auunT/JjxTeTkJoNu8KgS/LdrvISpO67TkQziZo9XD5FOzSN7N3e4f8vO4N4fpjgkIDH/9wyEYe0zYz34xMAFlnhZzqrHycRqzBJuMxGqlFQcKWp9IisLMoVJhLrnvbDLuwwcjeqYkhvODjSs7UDKwTE4X4WmvZr0x4kOclOeAAz/pM6oNVnjgWJd9SnYtoa67bZVkne0k6mYjVhosie8v8icijmJ4OyLZUGWnjZCRd/TPkzQUw+B0yvsop9FYGidhCI+4MVx6W5w7SRtCctxVfCjLpmU4kWaBUUJ5YIQ5xm55yxEYuAsQkxOAYDCMFlV8ntWStYwIG1FsBgJX6VPevXuPPMjWiPNedIpJwBH2PLB4blxMfzDYuCeaIqU4daDaEWxxpuFTTK9fLdJKuipwFG6rwE3OuijeSN+2SLszi834DXtUjQdikHSTQG392+oTmZCFPeffLk/OiV2VpdXF3gGL7sr5M9hOWIZ783q0vW1l6nAElZ7UA//kW+L6QRxbnBVTJK5eCmMY6RJmL76zjqC1jQ0FC10', + }; + const res = await decryptConfig(config, repository); + expect(res.encrypted).toBeUndefined(); + expect(res.token).toBe('123'); + await expect(decryptConfig(config, 'wrong/org')).rejects.toThrow( + CONFIG_VALIDATION, + ); + }); + + it('handles PGP multi-org constraint', async () => { + GlobalConfig.set({ privateKey }); + config.encrypted = { + token: + 'wcFMAw+4H7SgaqGOAQ//Yk4RTQoLEhO0TKxN2IUBrCi88ts+CG1SXKeL06sJ2qikN/3n2JYAGGKgkHRICfu5dOnsjyFdLJ1XWUrbsM3XgVWikMbrmzD1Xe7N5DsoZXlt4Wa9pZ+IkZuE6XcKKu9whIJ22ciEwCzFwDmk/CBshdCCVVQ3IYuM6uibEHn/AHQ8K15XhraiSzF6DbJpevs5Cy7b5YHFyE936H25CVnouUQnMPsirpQq3pYeMq/oOtV/m4mfRUUQ7MUxvtrwE4lq4hLjFu5n9rwlcqaFPl7I7BEM++1c9LFpYsP5mTS7hHCZ9wXBqER8fa3fKYx0bK1ihCpjP4zUkR7P/uhWDArXamv7gHX2Kj/Qsbegn7KjTdZlggAmaJl/CuSgCbhySy+E55g3Z1QFajiLRpQ5+RsWFDbbI08YEgzyQ0yNCaRvrkgo7kZ1D95rEGRfY96duOQbjzOEqtvYmFChdemZ2+f9Kh/JH1+X9ynxY/zYe/0p/U7WD3QNTYN18loc4aXiB1adXD5Ka2QfNroLudQBmLaJpJB6wASFfuxddsD5yRnO32NSdRaqIWC1x6ti3ZYJZ2RsNwJExPDzjpQTuMOH2jtpu3q7NHmW3snRKy2YAL2UjI0YdeKIlhc/qLCJt9MRcOxWYvujTMD/yGprhG44qf0jjMkJBu7NjuVIMONujabl9b7SUQGfO/t+3rMuC68bQdCGLlO8gf3hvtD99utzXphi6idjC0HKSW/9KzuMkm+syGmIAYq/0L3EFvpZ38uq7z8KzwFFQHI3sBA34bNEr5zpU5OMWg', + }; + let res = await decryptConfig(config, repository); + expect(res.encrypted).toBeUndefined(); + expect(res.token).toBe('123'); + res = await decryptConfig(config, 'def/ghi'); + expect(res.encrypted).toBeUndefined(); + expect(res.token).toBe('123'); + await expect(decryptConfig(config, 'wrong/org')).rejects.toThrow( + CONFIG_VALIDATION, + ); + }); + + it('handles PGP org/repo constraint', async () => { + GlobalConfig.set({ privateKey }); + config.encrypted = { + token: + 'wcFMAw+4H7SgaqGOAQ//Wp7N0PaDZp0uOdwsc1CuqAq0UPcq+IQdHyKpJs3tHiCecXBHogy4P+rY9nGaUrVneCr4HexuKGuyJf1yl0ZqFffAUac5PjF8eDvjukQGOUq4aBlOogJCEefnuuVxVJx+NRR5iF1P6v57bmI1c+zoqZI/EQB30KU6O1BsdGPLUA/+R3dwCZd5Mbd36s34eYBasqcY9/QbqFcpElXMEPMse3kMCsVXPbZ+UMjtPJiBPUmtJq+ifnu1LzDrfshusSQMwgd/QNk7nEsijiYKllkWhHTP6g7zigvJ46x0h6AYS108YiuK3B9XUhXN9m05Ac6KTEEUdRI3E/dK2dQuRkLjXC8wceQm4A19Gm0uHoMIJYOCbiVoBCH6ayvKbZWZV5lZ4D1JbDNGmKeIj6OX9XWEMKiwTx0Xe89V7BdJzwIGrL0TCLtXuYWZ/R2k+UuBqtgzr44BsBqMpKUA0pcGBoqsEou1M05Ae9fJMF6ADezF5UQZPxT1hrMldiTp3p9iHGfWN2tKHeoW/8CqlIqg9JEkTc+Pl/L9E6ndy5Zjf097PvcmSGhxUQBE7XlrZoIlGhiEU/1HPMen0UUIs0LUu1ywpjCex2yTWnU2YmEwy0MQI1sekSr96QFxDDz9JcynYOYbqR/X9pdxEWyzQ+NJ3n6K97nE1Dj9Sgwu7mFGiUdNkf/SUAF0eZi/eXg71qumpMGBd4eWPtgkeMPLHjvMSYw9vBUfcoKFz6RJ4woG0dw5HOFkPnIjXKWllnl/o01EoBp/o8uswsIS9Nb8i+bp27U6tAHE', + }; + const res = await decryptConfig(config, repository); + expect(res.encrypted).toBeUndefined(); + expect(res.token).toBe('123'); + await expect(decryptConfig(config, 'abc/defg')).rejects.toThrow( + CONFIG_VALIDATION, + ); + }); + + it('handles PGP multi-org/repo constraint', async () => { + GlobalConfig.set({ privateKey }); + config.encrypted = { + token: + 'wcFMAw+4H7SgaqGOARAAibXL3zr0KZawiND868UGdPpGRo1aVZfn0NUBHpm8mXfgB1rBHaLsP7qa8vxDHpwH9DRD1IyB4vvPUwtu7wmuv1Vtr596tD40CCcCZYB5JjZLWRF0O0xaZFCOi7Z9SqqdaOQoMScyvPO+3/lJkS7zmLllJFH0mQoX5Cr+owUAMSWqbeCQ9r/KAXpnhmpraDjTav48WulcdTMc8iQ/DHimcdzHErLOAjtiQi4OUe1GnDCcN76KQ+c+ZHySnkXrYi/DhOOu9qB4glJ5n68NueFja+8iR39z/wqCI6V6TIUiOyjFN86iVyNPQ4Otem3KuNwrnwSABLDqP491eUNjT8DUDffsyhNC9lnjQLmtViK0EN2yLVpMdHq9cq8lszBChB7gobD9rm8nUHnTuLf6yJvZOj6toD5Yqj8Ibj58wN90Q8CUsBp9/qp0J+hBVUPOx4sT6kM2p6YarlgX3mrIW5c1U+q1eDbCddLjHiU5cW7ja7o+cqlA6mbDRu3HthjBweiXTicXZcRu1o/wy/+laQQ95x5FzAXDnOwQUHBmpTDI3tUJvQ+oy8XyBBbyC0LsBye2c2SLkPJ4Ai3IMR+Mh8puSzVywTbneiAQNBzJHlj5l85nCF2tUjvNo3dWC+9mU5sfXg11iEC6LRbg+icjpqRtTjmQURtciKDUbibWacwU5T/SVAGPXnW7adBOS0PZPIZQcSwjchOdOl0IjzBy6ofu7ODdn2CXZXi8zbevTICXsHvjnW4MAj5oXrStxK3LkWyM3YBOLe7sOfWvWz7n9TM3dHg032navQ', + }; + let res = await decryptConfig(config, repository); + expect(res.encrypted).toBeUndefined(); + expect(res.token).toBe('123'); + res = await decryptConfig(config, 'def/def'); + expect(res.encrypted).toBeUndefined(); + expect(res.token).toBe('123'); + await expect(decryptConfig(config, 'abc/defg')).rejects.toThrow( + CONFIG_VALIDATION, + ); + }); + }); +}); diff --git a/lib/config/decrypt/kbpgp.ts b/lib/config/decrypt/kbpgp.ts new file mode 100644 index 00000000000000..8ed18fe1c5069c --- /dev/null +++ b/lib/config/decrypt/kbpgp.ts @@ -0,0 +1,63 @@ +import * as kbpgp from '@renovatebot/kbpgp'; +import { logger } from '../../logger'; +import { regEx } from '../../util/regex'; + +export async function tryDecryptKbPgp( + privateKey: string, + encryptedStr: string, +): Promise { + if (encryptedStr.length < 500) { + // optimization during transition of public key -> pgp + return null; + } + try { + const pk = await new Promise((resolve, reject) => { + kbpgp.KeyManager.import_from_armored_pgp( + { + armored: privateKey.replace(regEx(/\n[ \t]+/g), '\n'), + }, + (err: Error, pk) => { + if (err) { + reject(err); + } else { + resolve(pk); + } + }, + ); + }); + + const ring = new kbpgp.keyring.KeyRing(); + ring.add_key_manager(pk); + + const startBlock = '-----BEGIN PGP MESSAGE-----\n\n'; + const endBlock = '\n-----END PGP MESSAGE-----'; + let armoredMessage = encryptedStr.trim(); + if (!armoredMessage.startsWith(startBlock)) { + armoredMessage = `${startBlock}${armoredMessage}`; + } + if (!armoredMessage.endsWith(endBlock)) { + armoredMessage = `${armoredMessage}${endBlock}`; + } + + const data = await new Promise((resolve, reject) => { + kbpgp.unbox( + { + keyfetch: ring, + armored: armoredMessage, + }, + (err: Error, literals: any) => { + if (err) { + reject(err); + } else { + resolve(literals[0].toString()); + } + }, + ); + }); + logger.debug('Decrypted config using kppgp'); + return data as string; + } catch (err) { + logger.debug({ err }, 'Could not decrypt using kppgp'); + return null; + } +} diff --git a/lib/config/decrypt/legacy.spec.ts b/lib/config/decrypt/legacy.spec.ts new file mode 100644 index 00000000000000..c36943526e319c --- /dev/null +++ b/lib/config/decrypt/legacy.spec.ts @@ -0,0 +1,97 @@ +import { Fixtures } from '../../../test/fixtures'; +import { CONFIG_VALIDATION } from '../../constants/error-messages'; +import { decryptConfig } from '../decrypt'; +import { GlobalConfig } from '../global'; +import type { RenovateConfig } from '../types'; + +const privateKey = Fixtures.get('private.pem', '..'); +const repository = 'abc/def'; + +describe('config/decrypt/legacy', () => { + describe('decryptConfig()', () => { + let config: RenovateConfig; + + beforeEach(() => { + config = {}; + GlobalConfig.reset(); + }); + + it('handles invalid encrypted type', async () => { + config.encrypted = 1; + GlobalConfig.set({ privateKey }); + const res = await decryptConfig(config, repository); + expect(res.encrypted).toBeUndefined(); + }); + + it('handles invalid encrypted value', async () => { + config.encrypted = { a: 1 }; + GlobalConfig.set({ privateKey, privateKeyOld: 'invalid-key' }); + await expect(decryptConfig(config, repository)).rejects.toThrow( + CONFIG_VALIDATION, + ); + }); + + it('replaces npm token placeholder in npmrc', async () => { + GlobalConfig.set({ + privateKey: 'invalid-key', + privateKeyOld: privateKey, + }); // test old key failover + config.npmrc = + '//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n'; + config.encrypted = { + npmToken: + 'FLA9YHIzpE7YetAg/P0X46npGRCMqn7hgyzwX5ZQ9wYgu9BRRbTiBVsUIFTyM5BuP1Q22slT2GkWvFvum7GU236Y6QiT7Nr8SLvtsJn2XUuq8H7REFKzdy3+wqyyWbCErYTFyY1dcPM7Ht+CaGDWdd8u/FsoX7AdMRs/X1jNUo6iSmlUiyGlYDKF+QMnCJom1VPVgZXWsGKdjI2MLny991QMaiv0VajmFIh4ENv4CtXOl/1twvIl/6XTXAaqpJJKDTPZEuydi+PHDZmal2RAOfrkH4m0UURa7SlfpUlIg+EaqbNGp85hCYXLwRcEET1OnYr3rH1oYkcYJ40any1tvQ==', + }; + const res = await decryptConfig(config, repository); + expect(res.encrypted).toBeUndefined(); + expect(res.npmToken).toBeUndefined(); + expect(res.npmrc).toBe( + '//registry.npmjs.org/:_authToken=abcdef-ghijklm-nopqf-stuvwxyz\n//registry.npmjs.org/:_authToken=abcdef-ghijklm-nopqf-stuvwxyz\n', + ); + }); + + it('appends npm token in npmrc', async () => { + GlobalConfig.set({ privateKey }); + config.npmrc = 'foo=bar\n'; + config.encrypted = { + npmToken: + 'FLA9YHIzpE7YetAg/P0X46npGRCMqn7hgyzwX5ZQ9wYgu9BRRbTiBVsUIFTyM5BuP1Q22slT2GkWvFvum7GU236Y6QiT7Nr8SLvtsJn2XUuq8H7REFKzdy3+wqyyWbCErYTFyY1dcPM7Ht+CaGDWdd8u/FsoX7AdMRs/X1jNUo6iSmlUiyGlYDKF+QMnCJom1VPVgZXWsGKdjI2MLny991QMaiv0VajmFIh4ENv4CtXOl/1twvIl/6XTXAaqpJJKDTPZEuydi+PHDZmal2RAOfrkH4m0UURa7SlfpUlIg+EaqbNGp85hCYXLwRcEET1OnYr3rH1oYkcYJ40any1tvQ==', + }; + const res = await decryptConfig(config, repository); + expect(res.encrypted).toBeUndefined(); + expect(res.npmToken).toBeUndefined(); + expect(res.npmrc).toBe( + `foo=bar\n_authToken=abcdef-ghijklm-nopqf-stuvwxyz\n`, + ); + }); + + it('decrypts nested', async () => { + GlobalConfig.set({ privateKey }); + config.packageFiles = [ + { + packageFile: 'package.json', + devDependencies: { + encrypted: { + branchPrefix: + 'FLA9YHIzpE7YetAg/P0X46npGRCMqn7hgyzwX5ZQ9wYgu9BRRbTiBVsUIFTyM5BuP1Q22slT2GkWvFvum7GU236Y6QiT7Nr8SLvtsJn2XUuq8H7REFKzdy3+wqyyWbCErYTFyY1dcPM7Ht+CaGDWdd8u/FsoX7AdMRs/X1jNUo6iSmlUiyGlYDKF+QMnCJom1VPVgZXWsGKdjI2MLny991QMaiv0VajmFIh4ENv4CtXOl/1twvIl/6XTXAaqpJJKDTPZEuydi+PHDZmal2RAOfrkH4m0UURa7SlfpUlIg+EaqbNGp85hCYXLwRcEET1OnYr3rH1oYkcYJ40any1tvQ==', + npmToken: + 'FLA9YHIzpE7YetAg/P0X46npGRCMqn7hgyzwX5ZQ9wYgu9BRRbTiBVsUIFTyM5BuP1Q22slT2GkWvFvum7GU236Y6QiT7Nr8SLvtsJn2XUuq8H7REFKzdy3+wqyyWbCErYTFyY1dcPM7Ht+CaGDWdd8u/FsoX7AdMRs/X1jNUo6iSmlUiyGlYDKF+QMnCJom1VPVgZXWsGKdjI2MLny991QMaiv0VajmFIh4ENv4CtXOl/1twvIl/6XTXAaqpJJKDTPZEuydi+PHDZmal2RAOfrkH4m0UURa7SlfpUlIg+EaqbNGp85hCYXLwRcEET1OnYr3rH1oYkcYJ40any1tvQ==', + }, + }, + }, + 'backend/package.json', + ]; + // TODO: fix types #22198 + const res = (await decryptConfig(config, repository)) as any; + expect(res.encrypted).toBeUndefined(); + expect(res.packageFiles[0].devDependencies.encrypted).toBeUndefined(); + expect(res.packageFiles[0].devDependencies.branchPrefix).toBe( + 'abcdef-ghijklm-nopqf-stuvwxyz', + ); + expect(res.packageFiles[0].devDependencies.npmToken).toBeUndefined(); + expect(res.packageFiles[0].devDependencies.npmrc).toBe( + '//registry.npmjs.org/:_authToken=abcdef-ghijklm-nopqf-stuvwxyz\n', + ); + }); + }); +}); diff --git a/lib/config/decrypt/legacy.ts b/lib/config/decrypt/legacy.ts new file mode 100644 index 00000000000000..caa1e8d4058824 --- /dev/null +++ b/lib/config/decrypt/legacy.ts @@ -0,0 +1,40 @@ +/** istanbul ignore file */ +import crypto from 'node:crypto'; +import { logger } from '../../logger'; + +export function tryDecryptPublicKeyPKCS1( + privateKey: string, + encryptedStr: string, +): string | null { + let decryptedStr: string | null = null; + try { + decryptedStr = crypto + .privateDecrypt( + { + key: privateKey, + padding: crypto.constants.RSA_PKCS1_PADDING, + }, + Buffer.from(encryptedStr, 'base64'), + ) + .toString(); + } catch (err) { + logger.debug('Could not decrypt using PKCS1 padding'); + } + return decryptedStr; +} + +export function tryDecryptPublicKeyDefault( + privateKey: string, + encryptedStr: string, +): string | null { + let decryptedStr: string | null = null; + try { + decryptedStr = crypto + .privateDecrypt(privateKey, Buffer.from(encryptedStr, 'base64')) + .toString(); + logger.debug('Decrypted config using default padding'); + } catch (err) { + logger.debug('Could not decrypt using default padding'); + } + return decryptedStr; +} diff --git a/lib/config/decrypt/openpgp.spec.ts b/lib/config/decrypt/openpgp.spec.ts new file mode 100644 index 00000000000000..272b758df0c7d2 --- /dev/null +++ b/lib/config/decrypt/openpgp.spec.ts @@ -0,0 +1,145 @@ +import { Fixtures } from '../../../test/fixtures'; +import { CONFIG_VALIDATION } from '../../constants/error-messages'; +import { decryptConfig } from '../decrypt'; +import { GlobalConfig } from '../global'; +import type { RenovateConfig } from '../types'; + +const privateKey = Fixtures.get('private-pgp.pem', '..'); +const repository = 'abc/def'; + +describe('config/decrypt/openpgp', () => { + describe('decryptConfig()', () => { + let config: RenovateConfig; + + beforeAll(() => { + process.env.RENOVATE_X_USE_OPENPGP = 'true'; + }); + + beforeEach(() => { + jest.resetModules(); + config = {}; + GlobalConfig.reset(); + }); + + it('rejects invalid PGP message', async () => { + GlobalConfig.set({ privateKey }); + config.encrypted = { + token: + 'long-but-wrong-wcFMAw+4H7SgaqGOAQ//ZNPgHJ4RQBdfFoDX8Ywe9UxqMlc8k6VasCszQ2JULh/BpEdKdgRUGNaKaeZ+oBKYDBmDwAD5V5FEMlsg+KO2gykp/p2BAwvKGtYK0MtxLh4h9yJbN7TrVnGO3/cC+Inp8exQt0gD6f1Qo/9yQ9NE4/BIbaSs2b2DgeIK7Ed8N675AuSo73UOa6o7t+9pKeAAK5TQwgSvolihbUs8zjnScrLZD+nhvL3y5gpAqK9y//a+bTu6xPA1jdLjsswoCUq/lfVeVsB2GWV2h6eex/0fRKgN7xxNgdMn0a7msrvumhTawP8mPisPY2AAsHRIgQ9vdU5HbOPdGoIwI9n9rMdIRn9Dy7/gcX9Ic+RP2WwS/KnPHLu/CveY4W5bYqYoikWtJs9HsBCyWFiHIRrJF+FnXwtKdoptRfxTfJIkBoLrV6fDIyKo79iL+xxzgrzWs77KEJUJfexZBEGBCnrV2o7mo3SU197S0qx7HNvqrmeCj8CLxq8opXC71TNa+XE6BQUVyhMFxtW9LNxZUHRiNzrTSikArT4hzjyr3f9cb0kZVcs6XJQsm1EskU3WXo7ETD7nsukS9GfbwMn7tfYidB/yHSHl09ih871BcgByDmEKKdmamcNilW2bmTAqB5JmtaYT5/H8jRQWo/VGrEqlmiA4KmwSv7SZPlDnaDFrmzmMZZDSRgHe5KWl283XLmSeE8J0NPqwFH3PeOv4fIbOjJrnbnFBwSAsgsMe2K4OyFDh2COfrho7s8EP1Kl5lBkYJ+VRreGRerdSu24', + }; + await expect(decryptConfig(config, repository)).rejects.toThrow( + CONFIG_VALIDATION, + ); + config.encrypted = { + // Missing value + token: + 'wcFMAw+4H7SgaqGOAQ//ZNPgHJ4RQBdfFoDX8Ywe9UxqMlc8k6VasCszQ2JULh/BpEdKdgRUGNaKaeZ+oBKYDBmDwAD5V5FEMlsg+KO2gykp/p2BAwvKGtYK0MtxLh4h9yJbN7TrVnGO3/cC+Inp8exQt0gD6f1Qo/9yQ9NE4/BIbaSs2b2DgeIK7Ed8N675AuSo73UOa6o7t+9pKeAAK5TQwgSvolihbUs8zjnScrLZD+nhvL3y5gpAqK9y//a+bTu6xPA1jdLjsswoCUq/lfVeVsB2GWV2h6eex/0fRKgN7xxNgdMn0a7msrvumhTawP8mPisPY2AAsHRIgQ9vdU5HbOPdGoIwI9n9rMdIRn9Dy7/gcX9Ic+RP2WwS/KnPHLu/CveY4W5bYqYoikWtJs9HsBCyWFiHIRrJF+FnXwtKdoptRfxTfJIkBoLrV6fDIyKo79iL+xxzgrzWs77KEJUJfexZBEGBCnrV2o7mo3SU197S0qx7HNvqrmeCj8CLxq8opXC71TNa+XE6BQUVyhMFxtW9LNxZUHRiNzrTSikArT4hzjyr3f9cb0kZVcs6XJQsm1EskU3WXo7ETD7nsukS9GfbwMn7tfYidB/yHSHl09ih871BcgByDmEKKdmamcNilW2bmTAqB5JmtaYT5/H8jRQWo/VGrEqlmiA4KmwSv7SZPlDnaDFrmzmMZZDSRgHe5KWl283XLmSeE8J0NPqwFH3PeOv4fIbOjJrnbnFBwSAsgsMe2K4OyFDh2COfrho7s8EP1Kl5lBkYJ+VRreGRerdSu24', + }; + await expect(decryptConfig(config, repository)).rejects.toThrow( + CONFIG_VALIDATION, + ); + config.encrypted = { + // Missing org scope + token: + 'wcFMAw+4H7SgaqGOAQ//W38A3PmaZnE9XTCHGDQFD52Kz78UYnaiYeAT13cEqYWTwEvQ57B7D7I6i4jCLe7KwkUCS90kyoqd7twD75W/sO70MyIveKnMlqqnpkagQkFgmzMaXXNHaJXEkjzsflTELZu6UsUs/kZYmab7r14YLl9HbH/pqN9exil/9s3ym9URCPOyw/l04KWntdMAy0D+c5M4mE+obv6fz6nDb8tkdeT5Rt2uU+qw3gH1OsB2yu+zTWpI/xTGwDt5nB5txnNTsVrQ/ZK85MSktacGVcYuU9hsEDmSrShmtqlg6Myq+Hjb7cYAp2g4n13C/I3gGGaczl0PZaHD7ALMjI7p6O1q+Ix7vMxipiKMVjS3omJoqBCz3FKc6DVhyX4tfhxgLxFo0DpixNwGbBRbMBO8qZfUk7bicAl/oCRc2Ijmay5DDYuvtkw3G3Ou+sZTe6DNpWUFy6VA4ai7hhcLvcAuiYmLdwPISRR/X4ePa8ZrmSVPyVOvbmmwLhcDYSDlC9Mw4++7ELomlve5kvjVSHvPv9BPVb5sJF7gX4vOT4FrcKalQRPmhNCZrE8tY2lvlrXwV2EEhya8EYv4QTd3JUYEYW5FXiJrORK5KDTnISw+U02nFZjFlnoz9+R6h+aIT1crS3/+YjCHE/EIKvSftOnieYb02Gk7M9nqU19EYL9ApYw4+IjSRgFM3DShIrvuDwDkAwUfaq8mKtr9Vjg/r+yox//GKS3u3r4I3+dfCljA3OwskTPfbSD+huBk4mylIvaL5v8Fngxo979wiLw', + }; + await expect(decryptConfig(config, repository)).rejects.toThrow( + CONFIG_VALIDATION, + ); + config.encrypted = { + // Impossible to parse + token: + 'wcFMAw+4H7SgaqGOAQ//Wa/gHgQdH7tj3LQdW6rWKjzmkYVKZW9EbexJExu4WLaMgEKodlRMilcqCKfQZpjzoiC31J8Ly/x6Soury+lQnLVbtIQ4KWa/uCIz4lXCpPpGNgN2jPfOmdwWBMOcXIT+BgAMxRu3rAmvTtunrkACJ3J92eYNwJhTzp2Azn9LpT7kHnZ64z2SPhbdUgMMhCBwBG5BPArPzF5fdaqa8uUSbKhY0GMiqPXq6Zeq+EBNoPc/RJp2urpYTknO+nRb39avKjihd9MCZ/1d3QYymbRj7SZC3LJhenVF0hil3Uk8TBASnGQiDmBcIXQFhJ0cxavXqKjx+AEALq+kTdwGu5vuE2+2B820/o3lAXR9OnJHr8GodJ2ZBpzOaPrQe5zvxL0gLEeUUPatSOwuLhdo/6+bRCl2wNz23jIjDEFFTmsLqfEHcdVYVTH2QqvLjnUYcCRRuM32vS4rCMOEe0l6p0CV2rk22UZDIPcxqXjKucxse2Sow8ATWiPoIw7zWj7XBLqUKHFnMpPV2dCIKFKBsOKYgLjF4BvKzZJyhmVEPgMcKQLYqeT/2uWDR77NSWH0Cyiwk9M3KbOIMmV3pWh9PiXk6CvumECELbJHYH0Mc+P//BnbDq2Ie9dHdmKhFgRyHU7gWvkPhic9BX36xyldPcnhTgr1XWRoVe0ETGLDPCcqrQ/SUQGrLiujSOgxGu2K/6LDJhi4IKz1/nf7FUSj5eTIDqQiSPP5pXDjlH7oYxXXrHI/aYOCZ5sBx7mOzlEcENIrYblCHO/CYMTWdCJ4Wrftqk7K/A=', + }; + await expect(decryptConfig(config, repository)).rejects.toThrow( + CONFIG_VALIDATION, + ); + config.encrypted = { + token: 'too-short', + }; + await expect(decryptConfig(config, repository)).rejects.toThrow( + CONFIG_VALIDATION, + ); + }); + + it('handles PGP org constraint', async () => { + GlobalConfig.set({ privateKey }); + config.encrypted = { + token: + 'wcFMAw+4H7SgaqGOAQ/+Lz6RlbEymbnmMhrktuaGiDPWRNPEQFuMRwwYM6/B/r0JMZa9tskAA5RpyYKxGmJJeuRtlA8GkTw02GoZomlJf/KXJZ95FwSbkXMSRJRD8LJ2402Hw2TaOTaSvfamESnm8zhNo8cok627nkKQkyrpk64heVlU5LIbO2+UgYgbiSQjuXZiW+QuJ1hVRjx011FQgEYc59+22yuKYqd8rrni7TrVqhGRlHCAqvNAGjBI4H7uTFh0sP4auunT/JjxTeTkJoNu8KgS/LdrvISpO67TkQziZo9XD5FOzSN7N3e4f8vO4N4fpjgkIDH/9wyEYe0zYz34xMAFlnhZzqrHycRqzBJuMxGqlFQcKWp9IisLMoVJhLrnvbDLuwwcjeqYkhvODjSs7UDKwTE4X4WmvZr0x4kOclOeAAz/pM6oNVnjgWJd9SnYtoa67bZVkne0k6mYjVhosie8v8icijmJ4OyLZUGWnjZCRd/TPkzQUw+B0yvsop9FYGidhCI+4MVx6W5w7SRtCctxVfCjLpmU4kWaBUUJ5YIQ5xm55yxEYuAsQkxOAYDCMFlV8ntWStYwIG1FsBgJX6VPevXuPPMjWiPNedIpJwBH2PLB4blxMfzDYuCeaIqU4daDaEWxxpuFTTK9fLdJKuipwFG6rwE3OuijeSN+2SLszi834DXtUjQdikHSTQG392+oTmZCFPeffLk/OiV2VpdXF3gGL7sr5M9hOWIZ783q0vW1l6nAElZ7UA//kW+L6QRxbnBVTJK5eCmMY6RJmL76zjqC1jQ0FC10', + }; + const res = await decryptConfig(config, repository); + expect(res.encrypted).toBeUndefined(); + expect(res.token).toBe('123'); + await expect(decryptConfig(config, 'wrong/org')).rejects.toThrow( + CONFIG_VALIDATION, + ); + }); + + it('handles PGP multi-org constraint', async () => { + GlobalConfig.set({ privateKey }); + config.encrypted = { + token: + 'wcFMAw+4H7SgaqGOAQ//Yk4RTQoLEhO0TKxN2IUBrCi88ts+CG1SXKeL06sJ2qikN/3n2JYAGGKgkHRICfu5dOnsjyFdLJ1XWUrbsM3XgVWikMbrmzD1Xe7N5DsoZXlt4Wa9pZ+IkZuE6XcKKu9whIJ22ciEwCzFwDmk/CBshdCCVVQ3IYuM6uibEHn/AHQ8K15XhraiSzF6DbJpevs5Cy7b5YHFyE936H25CVnouUQnMPsirpQq3pYeMq/oOtV/m4mfRUUQ7MUxvtrwE4lq4hLjFu5n9rwlcqaFPl7I7BEM++1c9LFpYsP5mTS7hHCZ9wXBqER8fa3fKYx0bK1ihCpjP4zUkR7P/uhWDArXamv7gHX2Kj/Qsbegn7KjTdZlggAmaJl/CuSgCbhySy+E55g3Z1QFajiLRpQ5+RsWFDbbI08YEgzyQ0yNCaRvrkgo7kZ1D95rEGRfY96duOQbjzOEqtvYmFChdemZ2+f9Kh/JH1+X9ynxY/zYe/0p/U7WD3QNTYN18loc4aXiB1adXD5Ka2QfNroLudQBmLaJpJB6wASFfuxddsD5yRnO32NSdRaqIWC1x6ti3ZYJZ2RsNwJExPDzjpQTuMOH2jtpu3q7NHmW3snRKy2YAL2UjI0YdeKIlhc/qLCJt9MRcOxWYvujTMD/yGprhG44qf0jjMkJBu7NjuVIMONujabl9b7SUQGfO/t+3rMuC68bQdCGLlO8gf3hvtD99utzXphi6idjC0HKSW/9KzuMkm+syGmIAYq/0L3EFvpZ38uq7z8KzwFFQHI3sBA34bNEr5zpU5OMWg', + }; + let res = await decryptConfig(config, repository); + expect(res.encrypted).toBeUndefined(); + expect(res.token).toBe('123'); + res = await decryptConfig(config, 'def/ghi'); + expect(res.encrypted).toBeUndefined(); + expect(res.token).toBe('123'); + await expect(decryptConfig(config, 'wrong/org')).rejects.toThrow( + CONFIG_VALIDATION, + ); + }); + + it('handles PGP org/repo constraint', async () => { + GlobalConfig.set({ privateKey }); + config.encrypted = { + token: + 'wcFMAw+4H7SgaqGOAQ//Wp7N0PaDZp0uOdwsc1CuqAq0UPcq+IQdHyKpJs3tHiCecXBHogy4P+rY9nGaUrVneCr4HexuKGuyJf1yl0ZqFffAUac5PjF8eDvjukQGOUq4aBlOogJCEefnuuVxVJx+NRR5iF1P6v57bmI1c+zoqZI/EQB30KU6O1BsdGPLUA/+R3dwCZd5Mbd36s34eYBasqcY9/QbqFcpElXMEPMse3kMCsVXPbZ+UMjtPJiBPUmtJq+ifnu1LzDrfshusSQMwgd/QNk7nEsijiYKllkWhHTP6g7zigvJ46x0h6AYS108YiuK3B9XUhXN9m05Ac6KTEEUdRI3E/dK2dQuRkLjXC8wceQm4A19Gm0uHoMIJYOCbiVoBCH6ayvKbZWZV5lZ4D1JbDNGmKeIj6OX9XWEMKiwTx0Xe89V7BdJzwIGrL0TCLtXuYWZ/R2k+UuBqtgzr44BsBqMpKUA0pcGBoqsEou1M05Ae9fJMF6ADezF5UQZPxT1hrMldiTp3p9iHGfWN2tKHeoW/8CqlIqg9JEkTc+Pl/L9E6ndy5Zjf097PvcmSGhxUQBE7XlrZoIlGhiEU/1HPMen0UUIs0LUu1ywpjCex2yTWnU2YmEwy0MQI1sekSr96QFxDDz9JcynYOYbqR/X9pdxEWyzQ+NJ3n6K97nE1Dj9Sgwu7mFGiUdNkf/SUAF0eZi/eXg71qumpMGBd4eWPtgkeMPLHjvMSYw9vBUfcoKFz6RJ4woG0dw5HOFkPnIjXKWllnl/o01EoBp/o8uswsIS9Nb8i+bp27U6tAHE', + }; + const res = await decryptConfig(config, repository); + expect(res.encrypted).toBeUndefined(); + expect(res.token).toBe('123'); + await expect(decryptConfig(config, 'abc/defg')).rejects.toThrow( + CONFIG_VALIDATION, + ); + }); + + it('handles PGP multi-org/repo constraint', async () => { + GlobalConfig.set({ privateKey }); + config.encrypted = { + token: + 'wcFMAw+4H7SgaqGOARAAibXL3zr0KZawiND868UGdPpGRo1aVZfn0NUBHpm8mXfgB1rBHaLsP7qa8vxDHpwH9DRD1IyB4vvPUwtu7wmuv1Vtr596tD40CCcCZYB5JjZLWRF0O0xaZFCOi7Z9SqqdaOQoMScyvPO+3/lJkS7zmLllJFH0mQoX5Cr+owUAMSWqbeCQ9r/KAXpnhmpraDjTav48WulcdTMc8iQ/DHimcdzHErLOAjtiQi4OUe1GnDCcN76KQ+c+ZHySnkXrYi/DhOOu9qB4glJ5n68NueFja+8iR39z/wqCI6V6TIUiOyjFN86iVyNPQ4Otem3KuNwrnwSABLDqP491eUNjT8DUDffsyhNC9lnjQLmtViK0EN2yLVpMdHq9cq8lszBChB7gobD9rm8nUHnTuLf6yJvZOj6toD5Yqj8Ibj58wN90Q8CUsBp9/qp0J+hBVUPOx4sT6kM2p6YarlgX3mrIW5c1U+q1eDbCddLjHiU5cW7ja7o+cqlA6mbDRu3HthjBweiXTicXZcRu1o/wy/+laQQ95x5FzAXDnOwQUHBmpTDI3tUJvQ+oy8XyBBbyC0LsBye2c2SLkPJ4Ai3IMR+Mh8puSzVywTbneiAQNBzJHlj5l85nCF2tUjvNo3dWC+9mU5sfXg11iEC6LRbg+icjpqRtTjmQURtciKDUbibWacwU5T/SVAGPXnW7adBOS0PZPIZQcSwjchOdOl0IjzBy6ofu7ODdn2CXZXi8zbevTICXsHvjnW4MAj5oXrStxK3LkWyM3YBOLe7sOfWvWz7n9TM3dHg032navQ', + }; + let res = await decryptConfig(config, repository); + expect(res.encrypted).toBeUndefined(); + expect(res.token).toBe('123'); + res = await decryptConfig(config, 'def/def'); + expect(res.encrypted).toBeUndefined(); + expect(res.token).toBe('123'); + await expect(decryptConfig(config, 'abc/defg')).rejects.toThrow( + CONFIG_VALIDATION, + ); + }); + + it('fails to load openpgp', async () => { + jest.doMock('../../expose.cjs', () => ({ + openpgp: () => { + throw new Error('openpgp error'); + }, + })); + const pgp = await import('./openpgp'); + const { logger } = await import('../../logger'); + expect( + await pgp.tryDecryptOpenPgp( + '', + 'wcFMAw+4H7SgaqGOAQ/+Lz6RlbEymbnmMhrktuaGiDPWRNPEQFuMRwwYM6/B/r0JMZa9tskAA5RpyYKxGmJJeuRtlA8GkTw02GoZomlJf/KXJZ95FwSbkXMSRJRD8LJ2402Hw2TaOTaSvfamESnm8zhNo8cok627nkKQkyrpk64heVlU5LIbO2+UgYgbiSQjuXZiW+QuJ1hVRjx011FQgEYc59+22yuKYqd8rrni7TrVqhGRlHCAqvNAGjBI4H7uTFh0sP4auunT/JjxTeTkJoNu8KgS/LdrvISpO67TkQziZo9XD5FOzSN7N3e4f8vO4N4fpjgkIDH/9wyEYe0zYz34xMAFlnhZzqrHycRqzBJuMxGqlFQcKWp9IisLMoVJhLrnvbDLuwwcjeqYkhvODjSs7UDKwTE4X4WmvZr0x4kOclOeAAz/pM6oNVnjgWJd9SnYtoa67bZVkne0k6mYjVhosie8v8icijmJ4OyLZUGWnjZCRd/TPkzQUw+B0yvsop9FYGidhCI+4MVx6W5w7SRtCctxVfCjLpmU4kWaBUUJ5YIQ5xm55yxEYuAsQkxOAYDCMFlV8ntWStYwIG1FsBgJX6VPevXuPPMjWiPNedIpJwBH2PLB4blxMfzDYuCeaIqU4daDaEWxxpuFTTK9fLdJKuipwFG6rwE3OuijeSN+2SLszi834DXtUjQdikHSTQG392+oTmZCFPeffLk/OiV2VpdXF3gGL7sr5M9hOWIZ783q0vW1l6nAElZ7UA//kW+L6QRxbnBVTJK5eCmMY6RJmL76zjqC1jQ0FC10', + ), + ).toBeNull(); + expect(logger.warn).toHaveBeenCalled(); + expect(logger.once.warn).toHaveBeenCalled(); + }); + }); +}); diff --git a/lib/config/decrypt/openpgp.ts b/lib/config/decrypt/openpgp.ts new file mode 100644 index 00000000000000..5ec485da13f991 --- /dev/null +++ b/lib/config/decrypt/openpgp.ts @@ -0,0 +1,56 @@ +import { openpgp } from '../../expose.cjs'; +import { logger } from '../../logger'; +import { regEx } from '../../util/regex'; + +let pgp: typeof import('openpgp') | null | undefined = undefined; + +export async function tryDecryptOpenPgp( + privateKey: string, + encryptedStr: string, +): Promise { + if (encryptedStr.length < 500) { + // optimization during transition of public key -> pgp + return null; + } + if (pgp === undefined) { + try { + pgp = openpgp(); + } catch (err) { + logger.warn({ err }, 'Could load openpgp'); + pgp = null; + } + } + + if (pgp === null) { + logger.once.warn('Cannot load openpgp, skipping decryption'); + return null; + } + + try { + const pk = await pgp.readPrivateKey({ + // prettier-ignore + armoredKey: privateKey.replace(regEx(/\n[ \t]+/g), '\n'), // little massage to help a common problem + }); + const startBlock = '-----BEGIN PGP MESSAGE-----\n\n'; + const endBlock = '\n-----END PGP MESSAGE-----'; + let armoredMessage = encryptedStr.trim(); + if (!armoredMessage.startsWith(startBlock)) { + armoredMessage = `${startBlock}${armoredMessage}`; + } + if (!armoredMessage.endsWith(endBlock)) { + armoredMessage = `${armoredMessage}${endBlock}`; + } + const message = await pgp.readMessage({ + armoredMessage, + }); + const { data } = await pgp.decrypt({ + message, + decryptionKeys: pk, + }); + logger.debug('Decrypted config using openpgp'); + return data; + } catch (err) { + logger.debug({ err }, 'Could not decrypt using openpgp'); + return null; + } +} diff --git a/lib/config/global.ts b/lib/config/global.ts index e59c0ae0ca116a..1077299e45aca7 100644 --- a/lib/config/global.ts +++ b/lib/config/global.ts @@ -32,6 +32,12 @@ export class GlobalConfig { 'gitTimeout', 'platform', 'endpoint', + 'httpCacheTtlDays', + 'autodiscoverRepoSort', + 'autodiscoverRepoOrder', + 'mergeConfidenceEndpoint', + 'mergeConfidenceDatasources', + 'userAgent', ]; private static config: RepoGlobalConfig = {}; diff --git a/lib/config/index.spec.ts b/lib/config/index.spec.ts index 9a3e6a40c61fe9..d8d81d4e262213 100644 --- a/lib/config/index.spec.ts +++ b/lib/config/index.spec.ts @@ -1,5 +1,10 @@ import { getConfig } from './defaults'; -import { filterConfig, getManagerConfig, mergeChildConfig } from './index'; +import { + filterConfig, + getManagerConfig, + mergeChildConfig, + removeGlobalConfig, +} from './index'; jest.mock('../modules/datasource/npm'); jest.mock('../../config.js', () => ({}), { virtual: true }); @@ -131,4 +136,20 @@ describe('config/index', () => { expect(config.vulnerabilitySeverity).toBe('CRITICAL'); }); }); + + describe('removeGlobalConfig()', () => { + it('removes all global config', () => { + const filteredConfig = removeGlobalConfig(defaultConfig, false); + expect(filteredConfig).not.toHaveProperty('onboarding'); + expect(filteredConfig).not.toHaveProperty('binarySource'); + expect(filteredConfig.prHourlyLimit).toBe(2); + }); + + it('retains inherited config', () => { + const filteredConfig = removeGlobalConfig(defaultConfig, true); + expect(filteredConfig).toHaveProperty('onboarding'); + expect(filteredConfig).not.toHaveProperty('binarySource'); + expect(filteredConfig.prHourlyLimit).toBe(2); + }); + }); }); diff --git a/lib/config/index.ts b/lib/config/index.ts index 869a8250e5a9da..0494348f4849a1 100644 --- a/lib/config/index.ts +++ b/lib/config/index.ts @@ -31,6 +31,22 @@ export function getManagerConfig( return managerConfig; } +export function removeGlobalConfig( + config: RenovateConfig, + keepInherited: boolean, +): RenovateConfig { + const outputConfig: RenovateConfig = { ...config }; + for (const option of options.getOptions()) { + if (keepInherited && option.inheritConfigSupport) { + continue; + } + if (option.globalOnly) { + delete outputConfig[option.name]; + } + } + return outputConfig; +} + export function filterConfig( inputConfig: AllConfig, targetStage: RenovateConfigStage, @@ -39,6 +55,7 @@ export function filterConfig( const outputConfig: RenovateConfig = { ...inputConfig }; const stages: (string | undefined)[] = [ 'global', + 'inherit', 'repository', 'package', 'branch', diff --git a/lib/config/migrate-validate.ts b/lib/config/migrate-validate.ts index a3d8f8b42c62a8..2ba19032334fe4 100644 --- a/lib/config/migrate-validate.ts +++ b/lib/config/migrate-validate.ts @@ -32,7 +32,7 @@ export async function migrateAndValidate( }: { warnings: ValidationMessage[]; errors: ValidationMessage[]; - } = await configValidation.validateConfig(false, massagedConfig); + } = await configValidation.validateConfig('repo', massagedConfig); // istanbul ignore if if (is.nonEmptyArray(warnings)) { logger.warn({ warnings }, 'Found renovate config warnings'); diff --git a/lib/config/migrations/custom/node-migration.spec.ts b/lib/config/migrations/custom/node-migration.spec.ts index f9a7ec48fadb80..7171ebc41db193 100644 --- a/lib/config/migrations/custom/node-migration.spec.ts +++ b/lib/config/migrations/custom/node-migration.spec.ts @@ -12,7 +12,7 @@ describe('config/migrations/custom/node-migration', () => { ); }); - it('should not delete node incase it has more than one property', () => { + it('should not delete node in case it has more than one property', () => { expect(NodeMigration).toMigrate( { node: { enabled: true, automerge: false }, diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts index 4b441f962d4770..812c4b53948be4 100644 --- a/lib/config/options/index.ts +++ b/lib/config/options/index.ts @@ -2,9 +2,17 @@ import { getManagers } from '../../modules/manager'; import { getCustomManagers } from '../../modules/manager/custom'; import { getPlatformList } from '../../modules/platform'; import { getVersioningList } from '../../modules/versioning'; +import { supportedDatasources } from '../presets/internal/merge-confidence'; import type { RenovateOptions } from '../types'; const options: RenovateOptions[] = [ + { + name: 'mode', + description: 'Mode of operation.', + type: 'string', + default: 'full', + allowedValues: ['full', 'silent'], + }, { name: 'allowedHeaders', description: @@ -13,6 +21,27 @@ const options: RenovateOptions[] = [ default: ['X-*'], subType: 'string', globalOnly: true, + patternMatch: true, + }, + { + name: 'autodiscoverRepoOrder', + description: + 'The order method for autodiscover server side repository search.', + type: 'string', + default: null, + globalOnly: true, + allowedValues: ['asc', 'desc'], + supportedPlatforms: ['gitea'], + }, + { + name: 'autodiscoverRepoSort', + description: + 'The sort method for autodiscover server side repository search.', + type: 'string', + default: null, + globalOnly: true, + allowedValues: ['alpha', 'created', 'updated', 'size', 'id'], + supportedPlatforms: ['gitea'], }, { name: 'allowedEnv', @@ -22,6 +51,7 @@ const options: RenovateOptions[] = [ default: [], subType: 'string', globalOnly: true, + patternMatch: true, }, { name: 'detectGlobalManagerConfig', @@ -39,6 +69,25 @@ const options: RenovateOptions[] = [ default: false, globalOnly: true, }, + { + name: 'mergeConfidenceEndpoint', + description: + 'If set, Renovate will query this API for Merge Confidence data.', + type: 'string', + default: 'https://developer.mend.io/', + advancedUse: true, + globalOnly: true, + }, + { + name: 'mergeConfidenceDatasources', + description: + 'If set, Renovate will query the merge-confidence JSON API only for datasources that are part of this list.', + allowedValues: supportedDatasources, + default: supportedDatasources, + type: 'array', + subType: 'string', + globalOnly: true, + }, { name: 'useCloudMetadataServices', description: @@ -47,6 +96,14 @@ const options: RenovateOptions[] = [ default: true, globalOnly: true, }, + { + name: 'userAgent', + description: + 'If set to any string, Renovate will use this as the `user-agent` it sends with HTTP requests.', + type: 'string', + default: null, + globalOnly: true, + }, { name: 'allowPostUpgradeCommandTemplating', description: @@ -122,6 +179,7 @@ const options: RenovateOptions[] = [ type: 'string', default: 'renovate/configure', globalOnly: true, + inheritConfigSupport: true, cli: false, }, { @@ -131,6 +189,7 @@ const options: RenovateOptions[] = [ type: 'string', default: null, globalOnly: true, + inheritConfigSupport: true, cli: false, }, { @@ -140,6 +199,7 @@ const options: RenovateOptions[] = [ type: 'string', default: 'renovate.json', globalOnly: true, + inheritConfigSupport: true, cli: false, }, { @@ -148,6 +208,7 @@ const options: RenovateOptions[] = [ type: 'boolean', default: false, globalOnly: true, + inheritConfigSupport: true, }, { name: 'onboardingPrTitle', @@ -156,6 +217,7 @@ const options: RenovateOptions[] = [ type: 'string', default: 'Configure Renovate', globalOnly: true, + inheritConfigSupport: true, cli: false, }, { @@ -289,7 +351,6 @@ const options: RenovateOptions[] = [ allowedValues: ['disabled', 'enabled', 'reset'], stage: 'repository', default: 'disabled', - experimental: true, }, { name: 'repositoryCacheType', @@ -299,6 +360,23 @@ const options: RenovateOptions[] = [ type: 'string', stage: 'repository', default: 'local', + }, + { + name: 'reportType', + description: 'Set how, or if, reports should be generated.', + globalOnly: true, + type: 'string', + default: null, + experimental: true, + allowedValues: ['logging', 'file', 's3'], + }, + { + name: 'reportPath', + description: + 'Path to where the file should be written. In case of `s3` this has to be a full S3 URI.', + globalOnly: true, + type: 'string', + default: null, experimental: true, }, { @@ -434,7 +512,7 @@ const options: RenovateOptions[] = [ description: 'Change this value to override the default Renovate sidecar image.', type: 'string', - default: 'ghcr.io/containerbase/sidecar:10.2.1', + default: 'ghcr.io/containerbase/sidecar:10.6.14', globalOnly: true, }, { @@ -491,6 +569,7 @@ const options: RenovateOptions[] = [ stage: 'repository', type: 'boolean', globalOnly: true, + inheritConfigSupport: true, }, { name: 'onboardingConfig', @@ -499,6 +578,7 @@ const options: RenovateOptions[] = [ type: 'object', default: { $schema: 'https://docs.renovatebot.com/renovate-schema.json' }, globalOnly: true, + inheritConfigSupport: true, mergeable: true, }, { @@ -530,6 +610,17 @@ const options: RenovateOptions[] = [ supportedPlatforms: ['gitlab'], globalOnly: true, }, + { + name: 'forkCreation', + description: + 'Whether to create forks as needed at runtime when running in "fork mode".', + stage: 'repository', + type: 'boolean', + globalOnly: true, + supportedPlatforms: ['github'], + experimental: true, + default: true, + }, { name: 'forkToken', description: 'Set a personal access token here to enable "fork mode".', @@ -556,6 +647,38 @@ const options: RenovateOptions[] = [ default: true, globalOnly: true, }, + { + name: 'inheritConfig', + description: + 'If `true`, Renovate will inherit configuration from the `inheritConfigFileName` file in `inheritConfigRepoName', + type: 'boolean', + default: false, + globalOnly: true, + }, + { + name: 'inheritConfigRepoName', + description: + 'Renovate will look in this repo for the `inheritConfigFileName`.', + type: 'string', + default: '{{parentOrg}}/renovate-config', + globalOnly: true, + }, + { + name: 'inheritConfigFileName', + description: + 'Renovate will look for this config file name in the `inheritConfigRepoName`.', + type: 'string', + default: 'org-inherited-config.json', + globalOnly: true, + }, + { + name: 'inheritConfigStrict', + description: + 'If `true`, any `inheritedConfig` fetch error will result in an aborted run.', + type: 'boolean', + default: false, + globalOnly: true, + }, { name: 'requireConfig', description: @@ -565,6 +688,7 @@ const options: RenovateOptions[] = [ default: 'required', allowedValues: ['required', 'optional', 'ignored'], globalOnly: true, + inheritConfigSupport: true, }, { name: 'optimizeForDisabled', @@ -842,7 +966,6 @@ const options: RenovateOptions[] = [ 'Skip installing modules/dependencies if lock file updating is possible without a full install.', type: 'boolean', default: null, - globalOnly: true, }, { name: 'autodiscover', @@ -871,7 +994,19 @@ const options: RenovateOptions[] = [ subType: 'string', default: null, globalOnly: true, - supportedPlatforms: ['gitlab'], + supportedPlatforms: ['gitea', 'gitlab'], + }, + { + name: 'autodiscoverProjects', + description: + 'Filter the list of autodiscovered repositories by project names.', + stage: 'global', + type: 'array', + subType: 'string', + default: null, + globalOnly: true, + supportedPlatforms: ['bitbucket'], + patternMatch: true, }, { name: 'autodiscoverTopics', @@ -1000,18 +1135,23 @@ const options: RenovateOptions[] = [ default: {}, additionalProperties: { type: 'string', - format: 'uri', }, supportedManagers: [ + 'ansible', + 'bitbucket-pipelines', + 'crossplane', + 'devcontainer', + 'docker-compose', + 'dockerfile', + 'droneci', + 'gitlabci', 'helm-requirements', - 'helmv3', 'helmfile', - 'gitlabci', - 'dockerfile', - 'docker-compose', + 'helmv3', 'kubernetes', - 'ansible', - 'droneci', + 'kustomize', + 'terraform', + 'vendir', 'woodpecker', ], }, @@ -1148,6 +1288,7 @@ const options: RenovateOptions[] = [ mergeable: true, cli: false, env: false, + patternMatch: true, }, { name: 'excludeRepositories', @@ -1252,7 +1393,6 @@ const options: RenovateOptions[] = [ mergeable: true, cli: false, env: false, - advancedUse: true, }, { name: 'excludeDepNames', @@ -1294,6 +1434,34 @@ const options: RenovateOptions[] = [ cli: false, env: false, }, + { + name: 'matchDepPrefixes', + description: + 'Dep names prefixes to match. Valid only within a `packageRules` object.', + type: 'array', + subType: 'string', + allowString: true, + stage: 'package', + parents: ['packageRules'], + mergeable: true, + cli: false, + env: false, + advancedUse: true, + }, + { + name: 'excludeDepPrefixes', + description: + 'Dep names prefixes to exclude. Valid only within a `packageRules` object.', + type: 'array', + subType: 'string', + allowString: true, + stage: 'package', + parents: ['packageRules'], + mergeable: true, + cli: false, + env: false, + advancedUse: true, + }, { name: 'matchPackagePatterns', description: @@ -1355,7 +1523,7 @@ const options: RenovateOptions[] = [ { name: 'matchCurrentValue', description: - 'A regex to match against the raw `currentValue` string of a dependency. Valid only within a `packageRules` object.', + 'A regex or glob pattern to match against the raw `currentValue` string of a dependency. Valid only within a `packageRules` object.', type: 'string', stage: 'package', parents: ['packageRules'], @@ -1377,7 +1545,7 @@ const options: RenovateOptions[] = [ { name: 'matchNewValue', description: - 'A regex to match against the raw `newValue` string of a dependency. Valid only within a `packageRules` object.', + 'A regex or glob pattern to match against the raw `newValue` string of a dependency. Valid only within a `packageRules` object.', type: 'string', stage: 'package', parents: ['packageRules'], @@ -1460,7 +1628,6 @@ const options: RenovateOptions[] = [ mergeable: true, cli: false, env: false, - experimental: true, }, { name: 'matchUpdateTypes', @@ -1498,7 +1665,7 @@ const options: RenovateOptions[] = [ cli: false, env: false, }, - // Version behaviour + // Version behavior { name: 'allowedVersions', description: @@ -1540,6 +1707,15 @@ const options: RenovateOptions[] = [ type: 'boolean', default: false, }, + { + name: 'separateMultipleMinor', + description: + 'If set to `true`, Renovate creates separate PRs for each `minor` stream.', + stage: 'package', + type: 'boolean', + default: false, + experimental: true, + }, { name: 'separateMinorPatch', description: @@ -1751,7 +1927,14 @@ const options: RenovateOptions[] = [ allowedValues: ['auto', 'never'], default: 'auto', }, - // PR Behaviour + // PR Behavior + { + name: 'keepUpdatedLabel', + description: + 'If set, users can add this label to PRs to request they be kept updated with the base branch.', + type: 'string', + supportedPlatforms: ['azure', 'gitea', 'github', 'gitlab'], + }, { name: 'rollbackPrs', description: @@ -1857,6 +2040,7 @@ const options: RenovateOptions[] = [ 'Set sorting priority for PR creation. PRs with higher priority are created first, negative priority last.', type: 'integer', default: 0, + parents: ['packageRules'], cli: false, env: false, }, @@ -1874,6 +2058,7 @@ const options: RenovateOptions[] = [ default: false, supportedPlatforms: ['bitbucket'], globalOnly: true, + inheritConfigSupport: true, }, // Automatic merging { @@ -1968,6 +2153,8 @@ const options: RenovateOptions[] = [ description: 'Branch name template.', type: 'string', default: '{{{branchPrefix}}}{{{additionalBranchPrefix}}}{{{branchTopic}}}', + deprecationMsg: + 'We strongly recommended that you avoid configuring this field directly. Please edit `branchPrefix`, `additionalBranchPrefix`, or `branchTopic` instead.', cli: false, }, { @@ -1991,6 +2178,8 @@ const options: RenovateOptions[] = [ type: 'string', default: '{{{commitMessagePrefix}}} {{{commitMessageAction}}} {{{commitMessageTopic}}} {{{commitMessageExtra}}} {{{commitMessageSuffix}}}', + deprecationMsg: + 'We deprecated editing the `commitMessage` directly, and we recommend you stop using this config option. Instead use config options like `commitMessageAction`, `commitMessageExtra`, and so on, to create the commit message you want.', cli: false, }, { @@ -2061,9 +2250,11 @@ const options: RenovateOptions[] = [ { name: 'prTitle', description: - 'Pull Request title template (deprecated). Inherits from `commitMessage` if null.', + 'Pull Request title template. Inherits from `commitMessage` if null.', type: 'string', default: null, + deprecationMsg: + 'Direct editing of `prTitle` is now deprecated. Instead use config options like `commitMessageAction`, `commitMessageExtra`, and so on, as they will be passed through to `prTitle`.', cli: false, }, { @@ -2092,6 +2283,7 @@ const options: RenovateOptions[] = [ description: 'Customize sections in the Dependency Dashboard issue.', type: 'object', default: {}, + freeChoice: true, additionalProperties: { type: 'string', }, @@ -2344,6 +2536,16 @@ const options: RenovateOptions[] = [ cli: false, env: false, }, + { + name: 'readOnly', + description: + 'Match against requests that only read data and do not mutate anything.', + type: 'boolean', + stage: 'repository', + parents: ['hostRules'], + cli: false, + env: false, + }, { name: 'timeout', description: 'Timeout (in milliseconds) for queries to external endpoints.', @@ -2538,13 +2740,13 @@ const options: RenovateOptions[] = [ Pending: '{{{displayPending}}}', References: '{{{references}}}', 'Package file': '{{{packageFile}}}', - Age: "[![age](https://developer.mend.io/api/mc/badges/age/{{datasource}}/{{replace '/' '%2f' depName}}/{{{newVersion}}}?slim=true)](https://docs.renovatebot.com/merge-confidence/)", + Age: "{{#if newVersion}}[![age](https://developer.mend.io/api/mc/badges/age/{{datasource}}/{{replace '/' '%2f' depName}}/{{{newVersion}}}?slim=true)](https://docs.renovatebot.com/merge-confidence/){{/if}}", Adoption: - "[![adoption](https://developer.mend.io/api/mc/badges/adoption/{{datasource}}/{{replace '/' '%2f' depName}}/{{{newVersion}}}?slim=true)](https://docs.renovatebot.com/merge-confidence/)", + "{{#if newVersion}}[![adoption](https://developer.mend.io/api/mc/badges/adoption/{{datasource}}/{{replace '/' '%2f' depName}}/{{{newVersion}}}?slim=true)](https://docs.renovatebot.com/merge-confidence/){{/if}}", Passing: - "[![passing](https://developer.mend.io/api/mc/badges/compatibility/{{datasource}}/{{replace '/' '%2f' depName}}/{{{currentVersion}}}/{{{newVersion}}}?slim=true)](https://docs.renovatebot.com/merge-confidence/)", + "{{#if newVersion}}[![passing](https://developer.mend.io/api/mc/badges/compatibility/{{datasource}}/{{replace '/' '%2f' depName}}/{{{currentVersion}}}/{{{newVersion}}}?slim=true)](https://docs.renovatebot.com/merge-confidence/){{/if}}", Confidence: - "[![confidence](https://developer.mend.io/api/mc/badges/confidence/{{datasource}}/{{replace '/' '%2f' depName}}/{{{currentVersion}}}/{{{newVersion}}}?slim=true)](https://docs.renovatebot.com/merge-confidence/)", + "{{#if newVersion}}[![confidence](https://developer.mend.io/api/mc/badges/confidence/{{datasource}}/{{replace '/' '%2f' depName}}/{{{currentVersion}}}/{{{newVersion}}}?slim=true)](https://docs.renovatebot.com/merge-confidence/){{/if}}", }, }, { @@ -2878,6 +3080,14 @@ const options: RenovateOptions[] = [ default: null, supportedPlatforms: ['github'], }, + { + name: 'httpCacheTtlDays', + description: 'Maximum duration in days to keep HTTP cache entries.', + type: 'integer', + stage: 'repository', + default: 90, + globalOnly: true, + }, ]; export function getOptions(): RenovateOptions[] { diff --git a/lib/config/parse.ts b/lib/config/parse.ts new file mode 100644 index 00000000000000..45bbb8e651d434 --- /dev/null +++ b/lib/config/parse.ts @@ -0,0 +1,75 @@ +import jsonValidator from 'json-dup-key-validator'; +import JSON5 from 'json5'; +import upath from 'upath'; +import { logger } from '../logger'; +import { parseJson } from '../util/common'; + +export function parseFileConfig( + fileName: string, + fileContents: string, +): + | { success: true; parsedContents: unknown } + | { success: false; validationError: string; validationMessage: string } { + const fileType = upath.extname(fileName); + + if (fileType === '.json5') { + try { + return { success: true, parsedContents: JSON5.parse(fileContents) }; + } catch (err) /* istanbul ignore next */ { + logger.debug({ fileName, fileContents }, 'Error parsing JSON5 file'); + const validationError = 'Invalid JSON5 (parsing failed)'; + const validationMessage = `JSON5.parse error: \`${err.message.replaceAll( + '`', + "'", + )}\``; + return { + success: false, + validationError, + validationMessage, + }; + } + } else { + let allowDuplicateKeys = true; + let jsonValidationError = jsonValidator.validate( + fileContents, + allowDuplicateKeys, + ); + if (jsonValidationError) { + const validationError = 'Invalid JSON (parsing failed)'; + const validationMessage = jsonValidationError; + return { + success: false, + validationError, + validationMessage, + }; + } + allowDuplicateKeys = false; + jsonValidationError = jsonValidator.validate( + fileContents, + allowDuplicateKeys, + ); + if (jsonValidationError) { + const validationError = 'Duplicate keys in JSON'; + const validationMessage = JSON.stringify(jsonValidationError); + return { + success: false, + validationError, + validationMessage, + }; + } + try { + return { + success: true, + parsedContents: parseJson(fileContents, fileName), + }; + } catch (err) /* istanbul ignore next */ { + logger.debug({ fileContents }, 'Error parsing renovate config'); + const validationError = 'Invalid JSON (parsing failed)'; + const validationMessage = `JSON.parse error: \`${err.message.replaceAll( + '`', + "'", + )}\``; + return { success: false, validationError, validationMessage }; + } + } +} diff --git a/lib/config/presets/__snapshots__/index.spec.ts.snap b/lib/config/presets/__snapshots__/index.spec.ts.snap index 74ba7c4ff38165..b3fac71cdca566 100644 --- a/lib/config/presets/__snapshots__/index.spec.ts.snap +++ b/lib/config/presets/__snapshots__/index.spec.ts.snap @@ -119,8 +119,12 @@ exports[`config/presets/index resolvePreset resolves eslint 1`] = ` "matchPackageNames": [ "@types/eslint", "babel-eslint", + "@babel/eslint-parser", ], "matchPackagePrefixes": [ + "@eslint/", + "@stylistic/eslint-plugin", + "@types/eslint__", "@typescript-eslint/", "eslint", ], @@ -135,9 +139,11 @@ exports[`config/presets/index resolvePreset resolves linters 1`] = ` "matchPackageNames": [ "@types/eslint", "babel-eslint", + "@babel/eslint-parser", "friendsofphp/php-cs-fixer", "squizlabs/php_codesniffer", "symplify/easy-coding-standard", + "@stylistic/stylelint-plugin", "codelyzer", "prettier", "remark-lint", @@ -148,6 +154,9 @@ exports[`config/presets/index resolvePreset resolves linters 1`] = ` ], "matchPackagePrefixes": [ "ember-template-lint", + "@eslint/", + "@stylistic/eslint-plugin", + "@types/eslint__", "@typescript-eslint/", "eslint", "stylelint", @@ -169,9 +178,11 @@ exports[`config/presets/index resolvePreset resolves nested groups 1`] = ` "matchPackageNames": [ "@types/eslint", "babel-eslint", + "@babel/eslint-parser", "friendsofphp/php-cs-fixer", "squizlabs/php_codesniffer", "symplify/easy-coding-standard", + "@stylistic/stylelint-plugin", "codelyzer", "prettier", "remark-lint", @@ -182,6 +193,9 @@ exports[`config/presets/index resolvePreset resolves nested groups 1`] = ` ], "matchPackagePrefixes": [ "ember-template-lint", + "@eslint/", + "@stylistic/eslint-plugin", + "@types/eslint__", "@typescript-eslint/", "eslint", "stylelint", diff --git a/lib/config/presets/common.ts b/lib/config/presets/common.ts index 52cf7aa26d45d4..4fb427a4a3cfe9 100644 --- a/lib/config/presets/common.ts +++ b/lib/config/presets/common.ts @@ -29,6 +29,19 @@ export const removedPresets: Record = { 'helpers:oddIsUnstablePackages': null, 'group:jsTestMonMajor': 'group:jsTestNonMajor', 'github>whitesource/merge-confidence:beta': 'mergeConfidence:all-badges', + 'replacements:messageFormat-{{package}}-to-@messageformat/{{package}}': + 'replacements:messageFormat-to-scoped', + 'regexManagers:biomeVersions': 'customManagers:biomeVersions', + 'regexManagers:bitbucketPipelinesVersions': + 'customManagers:bitbucketPipelinesVersions', + 'regexManagers:dockerfileVersions': 'customManagers:dockerfileVersions', + 'regexManagers:githubActionsVersions': 'customManagers:githubActionsVersions', + 'regexManagers:gitlabPipelineVersions': + 'customManagers:gitlabPipelineVersions', + 'regexManagers:helmChartYamlAppVersions': + 'customManagers:helmChartYamlAppVersions', + 'regexManagers:mavenPropertyVersions': 'customManagers:mavenPropertyVersions', + 'regexManagers:tfvarsVersions': 'customManagers:tfvarsVersions', }; const renamedMonorepos: Record = { diff --git a/lib/config/presets/index.spec.ts b/lib/config/presets/index.spec.ts index 2a29243300c9c7..a55ae336459288 100644 --- a/lib/config/presets/index.spec.ts +++ b/lib/config/presets/index.spec.ts @@ -233,8 +233,20 @@ describe('config/presets/index', () => { config.extends = ['packages:eslint', 'packages:stylelint']; const res = await presets.resolveConfigPresets(config); expect(res).toEqual({ - matchPackageNames: ['@types/eslint', 'babel-eslint'], - matchPackagePrefixes: ['@typescript-eslint/', 'eslint', 'stylelint'], + matchPackageNames: [ + '@types/eslint', + 'babel-eslint', + '@babel/eslint-parser', + '@stylistic/stylelint-plugin', + ], + matchPackagePrefixes: [ + '@eslint/', + '@stylistic/eslint-plugin', + '@types/eslint__', + '@typescript-eslint/', + 'eslint', + 'stylelint', + ], }); }); @@ -250,8 +262,18 @@ describe('config/presets/index', () => { packageRules: [ { groupName: 'eslint', - matchPackageNames: ['@types/eslint', 'babel-eslint'], - matchPackagePrefixes: ['@typescript-eslint/', 'eslint'], + matchPackageNames: [ + '@types/eslint', + 'babel-eslint', + '@babel/eslint-parser', + ], + matchPackagePrefixes: [ + '@eslint/', + '@stylistic/eslint-plugin', + '@types/eslint__', + '@typescript-eslint/', + 'eslint', + ], }, ], }); @@ -261,16 +283,16 @@ describe('config/presets/index', () => { config.extends = ['packages:eslint']; const res = await presets.resolveConfigPresets(config); expect(res).toMatchSnapshot(); - expect(res.matchPackagePrefixes).toHaveLength(2); + expect(res.matchPackagePrefixes).toHaveLength(5); }); it('resolves linters', async () => { config.extends = ['packages:linters']; const res = await presets.resolveConfigPresets(config); expect(res).toMatchSnapshot(); - expect(res.matchPackageNames).toHaveLength(9); + expect(res.matchPackageNames).toHaveLength(11); expect(res.matchPackagePatterns).toHaveLength(1); - expect(res.matchPackagePrefixes).toHaveLength(4); + expect(res.matchPackagePrefixes).toHaveLength(7); }); it('resolves nested groups', async () => { @@ -279,9 +301,9 @@ describe('config/presets/index', () => { expect(res).toMatchSnapshot(); const rule = res.packageRules![0]; expect(rule.automerge).toBeTrue(); - expect(rule.matchPackageNames).toHaveLength(9); + expect(rule.matchPackageNames).toHaveLength(11); expect(rule.matchPackagePatterns).toHaveLength(1); - expect(rule.matchPackagePrefixes).toHaveLength(4); + expect(rule.matchPackagePrefixes).toHaveLength(7); }); it('migrates automerge in presets', async () => { @@ -314,6 +336,21 @@ describe('config/presets/index', () => { expect(res).toMatchSnapshot(); }); + it('resolves self-hosted preset with templating', async () => { + GlobalConfig.set({ customEnvVariables: { GIT_REF: 'abc123' } }); + config.extends = ['local>username/preset-repo#{{ env.GIT_REF }}']; + local.getPreset.mockImplementationOnce(({ tag }) => + tag === 'abc123' + ? Promise.resolve({ labels: ['self-hosted with template resolved'] }) + : Promise.reject(new Error('Failed to resolve self-hosted preset')), + ); + + const res = await presets.resolveConfigPresets(config); + + expect(res.labels).toEqual(['self-hosted with template resolved']); + expect(local.getPreset).toHaveBeenCalledOnce(); + }); + it('resolves self-hosted transitive presets without baseConfig', async () => { config.platform = 'gitlab'; config.endpoint = 'https://dummy.example.com/api/v4'; @@ -1040,6 +1077,14 @@ describe('config/presets/index', () => { `); }); + it('handles renamed regexManagers presets', async () => { + const res = await presets.getPreset( + 'regexManagers:dockerfileVersions', + {}, + ); + expect(res.customManagers).toHaveLength(1); + }); + it('gets linters', async () => { const res = await presets.getPreset('packages:linters', {}); expect(res).toMatchSnapshot(); diff --git a/lib/config/presets/index.ts b/lib/config/presets/index.ts index 827b1eef0768b2..93e8e1fc082399 100644 --- a/lib/config/presets/index.ts +++ b/lib/config/presets/index.ts @@ -10,6 +10,7 @@ import * as packageCache from '../../util/cache/package'; import { getTtlOverride } from '../../util/cache/package/decorator'; import { clone } from '../../util/clone'; import { regEx } from '../../util/regex'; +import * as template from '../../util/template'; import { GlobalConfig } from '../global'; import * as massage from '../massage'; import * as migration from '../migration'; @@ -148,6 +149,7 @@ export function parsePreset(input: string): ParsedPreset { const presetsPackages = [ 'compatibility', 'config', + 'customManagers', 'default', 'docker', 'group', @@ -157,7 +159,6 @@ export function parsePreset(input: string): ParsedPreset { 'npm', 'packages', 'preview', - 'regexManagers', 'replacements', 'schedule', 'security', @@ -320,6 +321,10 @@ export async function resolveConfigPresets( let config: AllConfig = {}; // First, merge all the preset configs from left to right if (inputConfig.extends?.length) { + // Compile templates + inputConfig.extends = inputConfig.extends.map((tmpl) => + template.compile(tmpl, {}), + ); for (const preset of inputConfig.extends) { if (shouldResolvePreset(preset, existingPresets, ignorePresets)) { logger.trace(`Resolving preset "${preset}"`); diff --git a/lib/config/presets/internal/regex-managers.spec.ts b/lib/config/presets/internal/custom-managers.spec.ts similarity index 69% rename from lib/config/presets/internal/regex-managers.spec.ts rename to lib/config/presets/internal/custom-managers.spec.ts index 2843cdc46bfb01..e869acdabe1e20 100644 --- a/lib/config/presets/internal/regex-managers.spec.ts +++ b/lib/config/presets/internal/custom-managers.spec.ts @@ -1,9 +1,170 @@ import { codeBlock } from 'common-tags'; import { regexMatches } from '../../../../test/util'; import { extractPackageFile } from '../../../modules/manager/custom/regex'; -import { presets } from './regex-managers'; +import { presets } from './custom-managers'; + +describe('config/presets/internal/custom-managers', () => { + describe('Update `$schema` version in biome.json', () => { + const customManager = presets['biomeVersions'].customManagers?.[0]; + + it(`find dependencies in file`, async () => { + const fileContent = codeBlock` + { + "$schema": "https://biomejs.dev/schemas/1.7.3/schema.json", + } + `; + + const res = await extractPackageFile( + fileContent, + 'biome.json', + customManager!, + ); + + expect(res?.deps).toMatchObject([ + { + currentValue: '1.7.3', + datasource: 'npm', + depName: '@biomejs/biome', + replaceString: '"https://biomejs.dev/schemas/1.7.3/schema.json"', + }, + ]); + }); + + describe('matches regexes patterns', () => { + it.each` + path | expected + ${'biome.json'} | ${true} + ${'biome.jsonc'} | ${true} + ${'foo/biome.json'} | ${true} + ${'foo/biome.jsonc'} | ${true} + ${'biome.yml'} | ${false} + `('$path', ({ path, expected }) => { + expect(regexMatches(path, customManager!.fileMatch)).toBe(expected); + }); + }); + }); + + describe('Update `_VERSION` variables in Bitbucket Pipelines', () => { + const customManager = + presets['bitbucketPipelinesVersions'].customManagers?.[0]; + + it(`find dependencies in file`, async () => { + const fileContent = codeBlock` + script: + # renovate: datasource=docker depName=node versioning=docker + - export NODE_VERSION=18 + + # renovate: datasource=npm depName=pnpm + - export PNPM_VERSION="7.25.1" + + # renovate: datasource=npm depName=yarn + - export YARN_VERSION 3.3.1 + + # renovate: datasource=custom.hashicorp depName=consul + - export CONSUL_VERSION 1.3.1 + + # renovate: datasource=github-releases depName=kubernetes-sigs/kustomize versioning=regex:^(?.+)/v(?\\d+)\\.(?\\d+)\\.(?\\d+)$ extractVersion=^kustomize/(?.+)$ + - export KUSTOMIZE_VERSION v5.2.1 + + - pipe: something/cool:latest + variables: + # renovate: datasource=docker depName=node versioning=docker + NODE_VERSION: 18 + # renovate: datasource=npm depName=pnpm + PNPM_VERSION:"7.25.1" + # renovate: datasource=npm depName=yarn + YARN_VERSION: '3.3.1' + + - echo $NODE_VERSION + `; + + const res = await extractPackageFile( + fileContent, + 'bitbucket-pipelines.yml', + customManager!, + ); + + expect(res?.deps).toMatchObject([ + { + currentValue: '18', + datasource: 'docker', + depName: 'node', + replaceString: + '# renovate: datasource=docker depName=node versioning=docker\n - export NODE_VERSION=18\n', + versioning: 'docker', + }, + { + currentValue: '7.25.1', + datasource: 'npm', + depName: 'pnpm', + replaceString: + '# renovate: datasource=npm depName=pnpm\n - export PNPM_VERSION="7.25.1"\n', + }, + { + currentValue: '3.3.1', + datasource: 'npm', + depName: 'yarn', + replaceString: + '# renovate: datasource=npm depName=yarn\n - export YARN_VERSION 3.3.1\n', + }, + { + currentValue: '1.3.1', + datasource: 'custom.hashicorp', + depName: 'consul', + replaceString: + '# renovate: datasource=custom.hashicorp depName=consul\n - export CONSUL_VERSION 1.3.1\n', + }, + { + currentValue: 'v5.2.1', + datasource: 'github-releases', + depName: 'kubernetes-sigs/kustomize', + replaceString: + '# renovate: datasource=github-releases depName=kubernetes-sigs/kustomize versioning=regex:^(?.+)/v(?\\d+)\\.(?\\d+)\\.(?\\d+)$ extractVersion=^kustomize/(?.+)$\n - export KUSTOMIZE_VERSION v5.2.1\n', + extractVersion: '^kustomize/(?.+)$', + versioning: + 'regex:^(?.+)/v(?\\d+)\\.(?\\d+)\\.(?\\d+)$', + }, + { + currentValue: '18', + datasource: 'docker', + depName: 'node', + replaceString: + '# renovate: datasource=docker depName=node versioning=docker\n NODE_VERSION: 18\n', + versioning: 'docker', + }, + { + currentValue: '7.25.1', + datasource: 'npm', + depName: 'pnpm', + replaceString: + '# renovate: datasource=npm depName=pnpm\n PNPM_VERSION:"7.25.1"\n', + }, + { + currentValue: '3.3.1', + datasource: 'npm', + depName: 'yarn', + replaceString: + "# renovate: datasource=npm depName=yarn\n YARN_VERSION: '3.3.1'\n", + }, + ]); + }); + + describe('matches regexes patterns', () => { + it.each` + path | expected + ${'bitbucket-pipelines.yml'} | ${true} + ${'bitbucket-pipelines.yaml'} | ${true} + ${'foo/bitbucket-pipelines.yml'} | ${true} + ${'foo/bitbucket-pipelines.yaml'} | ${true} + ${'foo/bar/bitbucket-pipelines.yml'} | ${true} + ${'foo/bar/bitbucket-pipelines.yaml'} | ${true} + ${'bitbucket-pipelines'} | ${false} + `('$path', ({ path, expected }) => { + expect(regexMatches(path, customManager!.fileMatch)).toBe(expected); + }); + }); + }); -describe('config/presets/internal/regex-managers', () => { describe('Update `_VERSION` variables in Dockerfiles', () => { const customManager = presets['dockerfileVersions'].customManagers?.[0]; @@ -17,6 +178,9 @@ describe('config/presets/internal/regex-managers', () => { # renovate: datasource=npm depName=pnpm ENV PNPM_VERSION="7.25.1" + # renovate: datasource=npm depName=pnpm + ENV PNPM_VERSION='7.25.1' + # renovate: datasource=npm depName=yarn ENV YARN_VERSION 3.3.1 @@ -51,6 +215,13 @@ describe('config/presets/internal/regex-managers', () => { replaceString: '# renovate: datasource=npm depName=pnpm\nENV PNPM_VERSION="7.25.1"\n', }, + { + currentValue: '7.25.1', + datasource: 'npm', + depName: 'pnpm', + replaceString: + "# renovate: datasource=npm depName=pnpm\nENV PNPM_VERSION='7.25.1'\n", + }, { currentValue: '3.3.1', datasource: 'npm', diff --git a/lib/config/presets/internal/regex-managers.ts b/lib/config/presets/internal/custom-managers.ts similarity index 73% rename from lib/config/presets/internal/regex-managers.ts rename to lib/config/presets/internal/custom-managers.ts index 857b0e28db8c36..6d772d39debe06 100644 --- a/lib/config/presets/internal/regex-managers.ts +++ b/lib/config/presets/internal/custom-managers.ts @@ -3,6 +3,33 @@ import type { Preset } from '../types'; /* eslint sort-keys: ["error", "asc", {caseSensitive: false, natural: true}] */ export const presets: Record = { + biomeVersions: { + customManagers: [ + { + customType: 'regex', + datasourceTemplate: 'npm', + depNameTemplate: '@biomejs/biome', + fileMatch: ['(^|/)biome.jsonc?$'], + matchStrings: [ + '"https://biomejs.dev/schemas/(?[^"]+)/schema.json"', + ], + }, + ], + description: + 'Update `$schema` version in `biome.json` configuration files.', + }, + bitbucketPipelinesVersions: { + customManagers: [ + { + customType: 'regex', + fileMatch: ['(^|/)bitbucket-pipelines\\.ya?ml$'], + matchStrings: [ + '# renovate: datasource=(?[a-z-.]+?) depName=(?[^\\s]+?)(?: (lookupName|packageName)=(?[^\\s]+?))?(?: versioning=(?[^\\s]+?))?(?: extractVersion=(?[^\\s]+?))?(?: registryUrl=(?[^\\s]+?))?\\s+.*\\s+[A-Za-z0-9_]+?_VERSION[ =:]\\s?["\']?(?.+?)["\']?\\s', + ], + }, + ], + description: 'Update `_VERSION` variables in Bitbucket Pipelines', + }, dockerfileVersions: { customManagers: [ { @@ -12,7 +39,7 @@ export const presets: Record = { '(^|/)([Dd]ocker|[Cc]ontainer)file[^/]*$', ], matchStrings: [ - '# renovate: datasource=(?[a-z-.]+?) depName=(?[^\\s]+?)(?: (lookupName|packageName)=(?[^\\s]+?))?(?: versioning=(?[^\\s]+?))?(?: extractVersion=(?[^\\s]+?))?(?: registryUrl=(?[^\\s]+?))?\\s(?:ENV|ARG) .+?_VERSION[ =]"?(?.+?)"?\\s', + '# renovate: datasource=(?[a-z-.]+?) depName=(?[^\\s]+?)(?: (lookupName|packageName)=(?[^\\s]+?))?(?: versioning=(?[^\\s]+?))?(?: extractVersion=(?[^\\s]+?))?(?: registryUrl=(?[^\\s]+?))?\\s(?:ENV|ARG)\\s+[A-Za-z0-9_]+?_VERSION[ =]["\']?(?.+?)["\']?\\s', ], }, ], @@ -40,7 +67,7 @@ export const presets: Record = { customType: 'regex', fileMatch: ['\\.gitlab-ci\\.ya?ml$'], matchStrings: [ - '# renovate: datasource=(?[a-z-.]+?) depName=(?[^\\s]+?)(?: (?:packageName)=(?[^\\s]+?))?(?: versioning=(?[^\\s]+?))?(?: extractVersion=(?[^\\s]+?))?\\s+[A-Za-z0-9_]+?_VERSION\\s*:\\s*["\']?(?.+?)["\']?\\s', + '# renovate: datasource=(?[a-z-.]+?) depName=(?[^\\s]+?)(?: (?:packageName)=(?[^\\s]+?))?(?: versioning=(?[^\\s]+?))?(?: extractVersion=(?[^\\s]+?))?(?: registryUrl=(?[^\\s]+?))?\\s+[A-Za-z0-9_]+?_VERSION\\s*:\\s*["\']?(?.+?)["\']?\\s', ], }, ], diff --git a/lib/config/presets/internal/default.ts b/lib/config/presets/internal/default.ts index 68895dc50d603e..29fe0081b4ce92 100644 --- a/lib/config/presets/internal/default.ts +++ b/lib/config/presets/internal/default.ts @@ -363,7 +363,7 @@ export const presets: Record = { }, pathSemanticCommitType: { description: - 'Use semanticCommitType `{{arg0}}` for all package files matching path `{{arg1}}`.', + 'Use semanticCommitType `{{arg1}}` for all package files matching path `{{arg0}}`.', packageRules: [ { matchFileNames: ['{{arg0}}'], @@ -574,6 +574,11 @@ export const presets: Record = { separateMajorMinor: true, separateMultipleMajor: true, }, + separateMultipleMinorReleases: { + description: + 'Separate each `minor` version of dependencies into individual branches/PRs.', + separateMultipleMinor: true, + }, separatePatchReleases: { description: 'Separate `patch` and `minor` releases of dependencies into separate PRs.', diff --git a/lib/config/presets/internal/index.spec.ts b/lib/config/presets/internal/index.spec.ts index fd439b817579e8..1b978f01d0d2b4 100644 --- a/lib/config/presets/internal/index.spec.ts +++ b/lib/config/presets/internal/index.spec.ts @@ -30,7 +30,7 @@ describe('config/presets/internal/index', () => { const config = await resolveConfigPresets( massageConfig(presetConfig), ); - const res = await validateConfig(false, config, true); + const res = await validateConfig('repo', config, true); expect(res.errors).toHaveLength(0); expect(res.warnings).toHaveLength(0); } catch (err) { @@ -43,4 +43,15 @@ describe('config/presets/internal/index', () => { } } } + + it('internal presets should not contain handlebars', () => { + Object.entries(internal.groups) + .map(([groupName, groupPresets]) => + Object.entries(groupPresets).map( + ([presetName]) => `${groupName}:${presetName}`, + ), + ) + .flat() + .forEach((preset) => expect(preset).not.toMatch(/{{.*}}/)); + }); }); diff --git a/lib/config/presets/internal/index.ts b/lib/config/presets/internal/index.ts index 37b564cdf1cf4e..5bc8c4181e2cee 100644 --- a/lib/config/presets/internal/index.ts +++ b/lib/config/presets/internal/index.ts @@ -1,5 +1,6 @@ import type { Preset, PresetConfig } from '../types'; import * as configPreset from './config'; +import * as customManagersPreset from './custom-managers'; import * as defaultPreset from './default'; import * as dockerPreset from './docker'; import * as groupPreset from './group'; @@ -9,7 +10,6 @@ import * as monorepoPreset from './monorepo'; import * as npm from './npm'; import * as packagesPreset from './packages'; import * as previewPreset from './preview'; -import * as regexManagersPreset from './regex-managers'; import * as replacements from './replacements'; import * as schedulePreset from './schedule'; import * as securityPreset from './security'; @@ -19,6 +19,7 @@ import * as workaroundsPreset from './workarounds'; export const groups: Record> = { config: configPreset.presets, + customManagers: customManagersPreset.presets, default: defaultPreset.presets, docker: dockerPreset.presets, group: groupPreset.presets, @@ -28,7 +29,6 @@ export const groups: Record> = { npm: npm.presets, packages: packagesPreset.presets, preview: previewPreset.presets, - regexManagers: regexManagersPreset.presets, replacements: replacements.presets, schedule: schedulePreset.presets, security: securityPreset.presets, diff --git a/lib/config/presets/internal/monorepo.ts b/lib/config/presets/internal/monorepo.ts index 6ab272fda827bc..d1b738816f5333 100644 --- a/lib/config/presets/internal/monorepo.ts +++ b/lib/config/presets/internal/monorepo.ts @@ -38,6 +38,7 @@ const repoGroups = { 'aspnet-api-versioning': 'https://github.com/Microsoft/aspnet-api-versioning', 'aspnet-health-checks': 'https://github.com/xabaril/AspNetCore.Diagnostics.HealthChecks', + astro: 'https://github.com/withastro/astro', 'automapper-dotnet': [ 'https://github.com/AutoMapper/AutoMapper', 'https://github.com/AutoMapper/AutoMapper.Extensions.Microsoft.DependencyInjection', @@ -47,6 +48,7 @@ const repoGroups = { 'https://github.com/awslabs/aws-lambda-powertools-typescript', 'https://github.com/aws-powertools/powertools-lambda-typescript', ], + 'aws-sdk-client-mock': 'https://github.com/m-radzikowski/aws-sdk-client-mock', 'aws-sdk-go': 'https://github.com/aws/aws-sdk-go', 'aws-sdk-go-v2': 'https://github.com/aws/aws-sdk-go-v2', 'aws-sdk-js-v3': 'https://github.com/aws/aws-sdk-js-v3', @@ -70,6 +72,8 @@ const repoGroups = { capacitor: 'https://github.com/ionic-team/capacitor', 'chakra-ui': 'https://github.com/chakra-ui/chakra-ui', chromely: 'https://github.com/chromelyapps/Chromely', + 'citation-js': 'https://github.com/citation-js/citation-js', + ckeditor: 'https://github.com/ckeditor/ckeditor5', clarity: 'https://github.com/vmware/clarity', clearscript: [ 'https://github.com/microsoft/ClearScript', @@ -273,8 +277,10 @@ const repoGroups = { 'ember-decorators': 'https://github.com/ember-decorators/ember-decorators', emojibase: 'https://github.com/milesj/emojibase', emotion: 'https://github.com/emotion-js/emotion', + eslint: 'https://github.com/eslint/eslint', 'eslint-config-globex': 'https://github.com/GlobexDesignsInc/eslint-config-globex', + 'eslint-stylistic': 'https://github.com/eslint-stylistic/eslint-stylistic', expo: 'https://github.com/expo/expo', 'fabric-chaincode-node': 'https://github.com/hyperledger/fabric-chaincode-node', @@ -333,8 +339,10 @@ const repoGroups = { 'json-smart-v2': 'https://github.com/netplex/json-smart-v2', jsplumb: 'https://github.com/jsplumb/jsplumb', junit5: 'https://github.com/junit-team/junit5', + kernelmemory: 'https://github.com/microsoft/kernel-memory', kotlin: 'https://github.com/JetBrains/kotlin', kroki: 'https://github.com/yuzutech/kroki', + ktor: 'https://github.com/ktorio/ktor', lamar: 'https://github.com/JasperFx/lamar', lerna: 'https://github.com/lerna/lerna', lexical: 'https://github.com/facebook/lexical', @@ -468,6 +476,7 @@ const repoGroups = { storybook: 'https://github.com/storybookjs/storybook', 'storybook-react-native': 'https://github.com/storybookjs/react-native', strapi: 'https://github.com/strapi/strapi', + strum: 'https://github.com/Peternator7/strum', 'stryker-js': 'https://github.com/stryker-mutator/stryker-js', surveyjs: 'https://github.com/surveyjs/surveyjs', 'swashbuckle-aspnetcore': @@ -491,6 +500,9 @@ const repoGroups = { 'theme-ui': 'https://github.com/system-ui/theme-ui', tika: 'https://github.com/apache/tika', tiptap: 'https://github.com/ueberdosis/tiptap', + 'tokio-prost': 'https://github.com/tokio-rs/prost', + 'tokio-tracing': 'https://github.com/tokio-rs/tracing', + tonic: 'https://github.com/hyperium/tonic', treat: 'https://github.com/seek-oss/treat', trpc: 'https://github.com/trpc/trpc', 'trust-dns': 'https://github.com/bluejekyll/trust-dns', @@ -510,6 +522,7 @@ const repoGroups = { vue: ['https://github.com/vuejs/vue', 'https://github.com/vuejs/core'], 'vue-cli': 'https://github.com/vuejs/vue-cli', vuepress: 'https://github.com/vuejs/vuepress', + weasel: 'https://github.com/JasperFx/weasel', 'web3-react': 'https://github.com/Uniswap/web3-react', webdriverio: 'https://github.com/webdriverio/webdriverio', wolverine: 'https://github.com/jasperfx/wolverine', @@ -559,6 +572,7 @@ const patternGroups = { spfx: ['^@microsoft/sp-', '^@microsoft/eslint-.+-spfx$'], spock: '^org\\.spockframework:spock-', 'syncfusion-dotnet': '^Syncfusion\\.', + 'testing-library': '^@testing-library/', wordpress: '^@wordpress/', }; diff --git a/lib/config/presets/internal/packages.ts b/lib/config/presets/internal/packages.ts index 227f715f862d49..daac49f540fa31 100644 --- a/lib/config/presets/internal/packages.ts +++ b/lib/config/presets/internal/packages.ts @@ -22,8 +22,18 @@ export const presets: Record = { }, eslint: { description: 'All ESLint packages.', - matchPackageNames: ['@types/eslint', 'babel-eslint'], - matchPackagePrefixes: ['@typescript-eslint/', 'eslint'], + matchPackageNames: [ + '@types/eslint', + 'babel-eslint', + '@babel/eslint-parser', + ], + matchPackagePrefixes: [ + '@eslint/', + '@stylistic/eslint-plugin', + '@types/eslint__', + '@typescript-eslint/', + 'eslint', + ], }, gatsby: { description: 'All packages published by Gatsby.', @@ -129,6 +139,7 @@ export const presets: Record = { }, stylelint: { description: 'All Stylelint packages.', + matchPackageNames: ['@stylistic/stylelint-plugin'], matchPackagePrefixes: ['stylelint'], }, test: { diff --git a/lib/config/presets/internal/replacements.ts b/lib/config/presets/internal/replacements.ts index a02e26131e7d51..74335ad3acd1bd 100644 --- a/lib/config/presets/internal/replacements.ts +++ b/lib/config/presets/internal/replacements.ts @@ -15,6 +15,8 @@ export const presets: Record = { 'replacements:containerbase', 'replacements:cpx-to-maintenance-fork', 'replacements:cucumber-to-scoped', + 'replacements:eslint-config-standard-with-typescript-to-eslint-config-love', + 'replacements:eslint-plugin-node-to-maintained-fork', 'replacements:fakerjs-to-scoped', 'replacements:fastify-to-scoped', 'replacements:hapi-to-scoped', @@ -22,6 +24,7 @@ export const presets: Record = { 'replacements:joi-to-scoped', 'replacements:joi-to-unscoped', 'replacements:k8s-registry-move', + 'replacements:mem-rename', 'replacements:middie-to-scoped', 'replacements:now-to-vercel', 'replacements:npm-run-all-to-maintenance-fork', @@ -36,12 +39,15 @@ export const presets: Record = { 'replacements:rollup-babel-to-scoped', 'replacements:rollup-json-to-scoped', 'replacements:rollup-node-resolve-to-scoped', + 'replacements:rollup-terser-to-scoped', 'replacements:rome-to-biome', 'replacements:semantic-release-replace-plugin-to-unscoped', 'replacements:spectre-cli-to-spectre-console-cli', + 'replacements:standard-version-to-commit-and-tag', 'replacements:vso-task-lib-to-azure-pipelines-task-lib', 'replacements:vsts-task-lib-to-azure-pipelines-task-lib', 'replacements:xmldom-to-scoped', + 'replacements:zap', ], ignoreDeps: [], // Hack to improve onboarding PR description }, @@ -169,6 +175,7 @@ export const presets: Record = { matchDatasources: ['docker'], matchPackageNames: ['ghcr.io/renovatebot/renovate'], matchPackagePatterns: ['^(?:docker\\.io/)?renovate/renovate$'], + versioning: 'semver', }, ], }, @@ -194,6 +201,32 @@ export const presets: Record = { }, ], }, + 'eslint-config-standard-with-typescript-to-eslint-config-love': { + description: + '`eslint-config-standard-with-typescript` was renamed to `eslint-config-love`.', + packageRules: [ + { + matchCurrentVersion: '^43.0.1', + matchDatasources: ['npm'], + matchPackageNames: ['eslint-config-standard-with-typescript'], + replacementName: 'eslint-config-love', + replacementVersion: '43.1.0', + }, + ], + }, + 'eslint-plugin-node-to-maintained-fork': { + description: + 'Replace stale `eslint-plugin-node` with a maintained fork: `eslint-plugin-n`.', + packageRules: [ + { + matchCurrentVersion: '^11.1.0', + matchDatasources: ['npm'], + matchPackageNames: ['eslint-plugin-node'], + replacementName: 'eslint-plugin-n', + replacementVersion: '14.0.0', + }, + ], + }, 'fakerjs-to-scoped': { description: '`fakerjs` packages became scoped.', packageRules: [ @@ -648,6 +681,18 @@ export const presets: Record = { }, ], }, + 'mem-rename': { + description: '`mem` was renamed to `memoize`.', + packageRules: [ + { + matchCurrentVersion: '^10.0.0', + matchDatasources: ['npm'], + matchPackageNames: ['mem'], + replacementName: 'memoize', + replacementVersion: '10.0.0', + }, + ], + }, 'middie-to-scoped': { description: '`middie` became scoped.', packageRules: [ @@ -810,6 +855,18 @@ export const presets: Record = { }, ], }, + 'rollup-terser-to-scoped': { + description: 'The terser plugin for rollup became scoped.', + packageRules: [ + { + matchCurrentVersion: '>=7.0.0', + matchDatasources: ['npm'], + matchPackageNames: ['rollup-plugin-terser'], + replacementName: '@rollup/plugin-terser', + replacementVersion: '0.1.0', + }, + ], + }, 'rome-to-biome': { description: 'The Rome repository is archived, and Biome is the community replacement. Read [the Biome announcement](https://biomejs.dev/blog/annoucing-biome/) for migration instructions.', @@ -846,6 +903,19 @@ export const presets: Record = { }, ], }, + 'standard-version-to-commit-and-tag': { + description: + '`standard-version` is now maintained as `commit-and-tag-version`.', + packageRules: [ + { + matchCurrentVersion: '^9.0.0', + matchDatasources: ['npm'], + matchPackageNames: ['standard-version'], + replacementName: 'commit-and-tag-version', + replacementVersion: '9.5.0', + }, + ], + }, 'vso-task-lib-to-azure-pipelines-task-lib': { description: 'The `vso-task-lib` package is now published as `azure-pipelines-task-lib`.', @@ -881,6 +951,35 @@ export const presets: Record = { }, ], }, + zap: { + description: 'Replace ZAP dependencies.', + packageRules: [ + { + description: + 'The `zap-stable` image has moved to the `zaproxy` organization.', + matchCurrentVersion: '>=2.0.0 <2.14.0', + matchDatasources: ['docker'], + matchPackagePatterns: [ + '^(?:docker\\.io/)?owasp/zap2docker-stable$', + '^(?:docker\\.io/)?softwaresecurityproject/zap-stable$', + ], + replacementName: 'zaproxy/zap-stable', + replacementVersion: '2.14.0', + }, + { + description: + 'The `zap-bare` image has moved to the `zaproxy` organization.', + matchCurrentVersion: '>=2.0.0 <2.14.0', + matchDatasources: ['docker'], + matchPackagePatterns: [ + '^(?:docker\\.io/)?owasp/zap2docker-bare$', + '^(?:docker\\.io/)?softwaresecurityproject/zap-bare$', + ], + replacementName: 'zaproxy/zap-bare', + replacementVersion: '2.14.0', + }, + ], + }, }; const muiReplacement: Replacement[] = [ @@ -913,7 +1012,7 @@ const mui: PresetTemplate = { const messageFormat: PresetTemplate = { description: - 'The `messageformat` monorepo package naming scheme changed from `messageFormat-{{package}}`-to-`@messageformat/{{package}}`.', + 'The `messageformat` monorepo package naming scheme changed from `messageFormat-{{package}}` to `@messageformat/{{package}}`.', packageRules: [ { matchCurrentVersion: '>=2.0.0 <3.0.0', @@ -940,7 +1039,7 @@ const messageFormat: PresetTemplate = { replacementVersion: '5.0.0', }, ], - title: 'messageFormat-{{package}}-to-@messageformat/{{package}}', + title: 'messageFormat-to-scoped', }; addPresets(presets, messageFormat, mui); diff --git a/lib/config/presets/internal/security.ts b/lib/config/presets/internal/security.ts index 6c19f6aaa62cb5..a5e72a45e21eec 100644 --- a/lib/config/presets/internal/security.ts +++ b/lib/config/presets/internal/security.ts @@ -21,4 +21,19 @@ export const presets: Record = { }, ], }, + 'only-security-updates': { + description: + 'Only update dependencies if vulnerabilities have been detected.', + extends: ['config:recommended'], + packageRules: [ + { + enabled: false, + matchPackageNames: ['*'], + }, + ], + vulnerabilityAlerts: { + enabled: true, + }, + osvVulnerabilityAlerts: true, + }, }; diff --git a/lib/config/presets/internal/workarounds.spec.ts b/lib/config/presets/internal/workarounds.spec.ts new file mode 100644 index 00000000000000..f0d8440bbb9917 --- /dev/null +++ b/lib/config/presets/internal/workarounds.spec.ts @@ -0,0 +1,45 @@ +import { regEx } from '../../../util/regex'; +import { presets } from './workarounds'; + +describe('config/presets/internal/workarounds', () => { + describe('bitnamiDockerImageVersioning', () => { + const versioning = presets.bitnamiDockerImageVersioning.packageRules![0] + .versioning as string; + const versioningRe = regEx(versioning.substring(6)); + const matchCurrentValue = presets.bitnamiDockerImageVersioning + .packageRules![0].matchCurrentValue as string; + const matchCurrentValueRe = regEx( + matchCurrentValue.substring(1, matchCurrentValue.length - 1), + ); + + it.each` + input | expected + ${'latest'} | ${false} + ${'20'} | ${true} + ${'20-debian'} | ${false} + ${'20-debian-12'} | ${true} + ${'1.24'} | ${true} + ${'1.24-debian-12'} | ${true} + ${'1.24.0'} | ${true} + ${'1.24.0-debian-12'} | ${true} + ${'1.24.0-debian-12-r24'} | ${true} + `('versioning("$input") == "$expected"', ({ input, expected }) => { + expect(versioningRe.test(input)).toEqual(expected); + }); + + it.each` + input | expected + ${'latest'} | ${false} + ${'20'} | ${false} + ${'20-debian'} | ${false} + ${'20-debian-12'} | ${true} + ${'1.24'} | ${false} + ${'1.24-debian-12'} | ${true} + ${'1.24.0'} | ${false} + ${'1.24.0-debian-12'} | ${true} + ${'1.24.0-debian-12-r24'} | ${true} + `('matchCurrentValue("$input") == "$expected"', ({ input, expected }) => { + expect(matchCurrentValueRe.test(input)).toEqual(expected); + }); + }); +}); diff --git a/lib/config/presets/internal/workarounds.ts b/lib/config/presets/internal/workarounds.ts index 7209fe67eb5844..158515222e43b0 100644 --- a/lib/config/presets/internal/workarounds.ts +++ b/lib/config/presets/internal/workarounds.ts @@ -21,9 +21,27 @@ export const presets: Record = { 'workarounds:disableEclipseLifecycleMapping', 'workarounds:disableMavenParentRoot', 'workarounds:containerbase', + 'workarounds:bitnamiDockerImageVersioning', ], ignoreDeps: [], // Hack to improve onboarding PR description }, + bitnamiDockerImageVersioning: { + description: 'Use custom regex versioning for bitnami images', + packageRules: [ + { + matchCurrentValue: + '/^(?\\d+)(?:\\.(?\\d+)(?:\\.(?\\d+))?)?-(?.+)-(?\\d+)(?:-r(?\\d+))?$/', + matchDatasources: ['docker'], + matchPackagePrefixes: [ + 'bitnami/', + 'docker.io/bitnami/', + 'gcr.io/bitnami-containers/', + ], + versioning: + 'regex:^(?\\d+)(?:\\.(?\\d+)(?:\\.(?\\d+))?)?(:?-(?.+)-(?\\d+)(?:-r(?\\d+))?)?$', + }, + ], + }, containerbase: { description: 'Add some containerbase overrides.', packageRules: [ @@ -62,6 +80,12 @@ export const presets: Record = { doNotUpgradeFromAlpineStableToEdge: { description: 'Do not upgrade from Alpine stable to edge.', packageRules: [ + { + allowedVersions: '<20000000', + matchCurrentVersion: '!/^\\d{8}$/', + matchDatasources: ['docker'], + matchDepNames: ['alpine'], + }, { allowedVersions: '<20000000', matchCurrentVersion: '!/^\\d{8}$/', @@ -125,7 +149,24 @@ export const presets: Record = { '^cimg/openjdk', ], versioning: - 'regex:^(?\\d+)?(\\.(?\\d+))?(\\.(?\\d+))?([\\._+](?\\d+))?(-(?.*))?$', + 'regex:^(?\\d+)?(\\.(?\\d+))?(\\.(?\\d+))?([\\._+](?(\\d\\.?)+)(LTS)?)?(-(?.*))?$', + }, + { + allowedVersions: '/^(?:8|11|17|21)(?:\\.|-|$)/', + description: + 'Limit Java runtime versions to LTS releases. To receive all major releases add `workarounds:javaLTSVersions` to the `ignorePresets` array.', + matchDatasources: ['docker', 'java-version'], + matchDepNames: [ + 'eclipse-temurin', + 'amazoncorretto', + 'adoptopenjdk', + 'openjdk', + 'java', + 'java-jre', + 'sapmachine', + ], + versioning: + 'regex:^(?\\d+)?(\\.(?\\d+))?(\\.(?\\d+))?([\\._+](?(\\d\\.?)+)(LTS)?)?(-(?.*))?$', }, ], }, diff --git a/lib/config/types.ts b/lib/config/types.ts index 6e0715dabd2d60..2129fd9b7ecc2b 100644 --- a/lib/config/types.ts +++ b/lib/config/types.ts @@ -2,12 +2,14 @@ import type { LogLevel } from 'bunyan'; import type { PlatformId } from '../constants'; import type { LogLevelRemap } from '../logger/types'; import type { CustomManager } from '../modules/manager/custom/types'; -import type { HostRule } from '../types'; +import type { RepoSortMethod, SortMethod } from '../modules/platform/types'; +import type { HostRule, SkipReason } from '../types'; import type { GitNoVerifyOption } from '../util/git/types'; import type { MergeConfidence } from '../util/merge-confidence/types'; export type RenovateConfigStage = | 'global' + | 'inherit' | 'repository' | 'package' | 'branch' @@ -28,69 +30,70 @@ export type RecreateWhen = 'auto' | 'never' | 'always'; // TODO: Proper typings export interface RenovateSharedConfig { $schema?: string; + addLabels?: string[]; + autoReplaceGlobalMatch?: boolean; automerge?: boolean; + automergeSchedule?: string[]; automergeStrategy?: MergeStrategy; - autoReplaceGlobalMatch?: boolean; - pruneBranchAfterAutomerge?: boolean; - branchPrefix?: string; - branchPrefixOld?: string; branchName?: string; branchNameStrict?: boolean; - manager?: string; + branchPrefix?: string; + branchPrefixOld?: string; commitMessage?: string; - commitMessagePrefix?: string; - commitMessageTopic?: string; commitMessageAction?: string; commitMessageExtra?: string; + commitMessageLowerCase?: 'auto' | 'never'; + commitMessagePrefix?: string; + commitMessageTopic?: string; confidential?: boolean; customChangelogUrl?: string; + dependencyDashboardApproval?: boolean; draftPR?: boolean; enabled?: boolean; enabledManagers?: string[]; extends?: string[]; fileMatch?: string[]; force?: RenovateConfig; + gitIgnoredAuthors?: string[]; group?: GroupConfig; groupName?: string; groupSlug?: string; - includePaths?: string[]; + hashedBranchLength?: number; ignoreDeps?: string[]; ignorePaths?: string[]; ignoreTests?: boolean; + includePaths?: string[]; internalChecksAsSuccess?: boolean; + keepUpdatedLabel?: string; labels?: string[]; - addLabels?: string[]; - dependencyDashboardApproval?: boolean; - hashedBranchLength?: number; + manager?: string; + milestone?: number; npmrc?: string; npmrcMerge?: boolean; + platformCommit?: boolean; postUpgradeTasks?: PostUpgradeTasks; prBodyColumns?: string[]; prBodyDefinitions?: Record; prCreation?: 'immediate' | 'not-pending' | 'status-success' | 'approval'; - productLinks?: Record; prPriority?: number; + productLinks?: Record; + pruneBranchAfterAutomerge?: boolean; rebaseLabel?: string; - respectLatest?: boolean; - stopUpdatingLabel?: string; rebaseWhen?: string; - recreateWhen?: RecreateWhen; recreateClosed?: boolean; + recreateWhen?: RecreateWhen; repository?: string; repositoryCache?: RepositoryCacheConfig; repositoryCacheType?: RepositoryCacheType; + respectLatest?: boolean; schedule?: string[]; - automergeSchedule?: string[]; - semanticCommits?: 'auto' | 'enabled' | 'disabled'; semanticCommitScope?: string | null; - commitMessageLowerCase?: 'auto' | 'never'; semanticCommitType?: string; + semanticCommits?: 'auto' | 'enabled' | 'disabled'; + stopUpdatingLabel?: string; suppressNotifications?: string[]; timezone?: string; unicodeEmoji?: boolean; - gitIgnoredAuthors?: string[]; - platformCommit?: boolean; - milestone?: number; } // Config options used only within the global worker @@ -99,26 +102,27 @@ export interface GlobalOnlyConfig { autodiscover?: boolean; autodiscoverFilter?: string[] | string; autodiscoverNamespaces?: string[]; + autodiscoverProjects?: string[]; autodiscoverTopics?: string[]; baseDir?: string; cacheDir?: string; containerbaseDir?: string; detectHostRulesFromEnv?: boolean; dockerCliOptions?: string; + endpoint?: string; forceCli?: boolean; gitNoVerify?: GitNoVerifyOption[]; gitPrivateKey?: string; globalExtends?: string[]; logFile?: string; logFileLevel?: LogLevel; + platform?: PlatformId; prCommitsPerRunLimit?: number; privateKeyPath?: string; privateKeyPathOld?: string; - redisUrl?: string; redisPrefix?: string; + redisUrl?: string; repositories?: RenovateRepository[]; - platform?: PlatformId; - endpoint?: string; useCloudMetadataServices?: boolean; } @@ -129,32 +133,38 @@ export interface RepoGlobalConfig { allowPlugins?: boolean; allowPostUpgradeCommandTemplating?: boolean; allowScripts?: boolean; + allowedEnv?: string[]; allowedHeaders?: string[]; allowedPostUpgradeCommands?: string[]; binarySource?: 'docker' | 'global' | 'install' | 'hermit'; + cacheDir?: string; cacheHardTtlMinutes?: number; cacheTtlOverride?: Record; + containerbaseDir?: string; customEnvVariables?: Record; dockerChildPrefix?: string; dockerCliOptions?: string; dockerSidecarImage?: string; dockerUser?: string; dryRun?: DryRunConfig; + endpoint?: string; executionTimeout?: number; - gitTimeout?: number; exposeAllEnv?: boolean; + gitTimeout?: number; githubTokenWarn?: boolean; + includeMirrors?: boolean; + localDir?: string; + mergeConfidenceEndpoint?: string; + mergeConfidenceDatasources?: string[]; migratePresets?: Record; + platform?: PlatformId; presetCachePersistence?: boolean; privateKey?: string; privateKeyOld?: string; - localDir?: string; - cacheDir?: string; - containerbaseDir?: string; - platform?: PlatformId; - endpoint?: string; - includeMirrors?: boolean; - allowedEnv?: string[]; + httpCacheTtlDays?: number; + autodiscoverRepoSort?: RepoSortMethod; + autodiscoverRepoOrder?: SortMethod; + userAgent?: string; } export interface LegacyAdminConfig { @@ -213,6 +223,8 @@ export interface RenovateConfig AssigneesAndReviewersConfig, ConfigMigration, Record { + reportPath?: string; + reportType?: 'logging' | 'file' | 's3' | null; depName?: string; baseBranches?: string[]; commitBody?: string; @@ -228,6 +240,11 @@ export interface RenovateConfig hostRules?: HostRule[]; + inheritConfig?: boolean; + inheritConfigFileName?: string; + inheritConfigRepoName?: string; + inheritConfigStrict?: boolean; + ignorePresets?: string[]; forkProcessing?: 'auto' | 'enabled' | 'disabled'; isFork?: boolean; @@ -340,33 +357,35 @@ export interface PackageRule UpdateConfig, Record { description?: string | string[]; + excludeDepNames?: string[]; + excludeDepPatterns?: string[]; + excludeDepPrefixes?: string[]; + excludePackageNames?: string[]; + excludePackagePatterns?: string[]; + excludePackagePrefixes?: string[]; + excludeRepositories?: string[]; isVulnerabilityAlert?: boolean; - matchFileNames?: string[]; matchBaseBranches?: string[]; + matchCategories?: string[]; + matchConfidence?: MergeConfidence[]; matchCurrentAge?: string; - matchManagers?: string[]; + matchCurrentValue?: string; + matchCurrentVersion?: string; matchDatasources?: string[]; - matchDepTypes?: string[]; matchDepNames?: string[]; matchDepPatterns?: string[]; + matchDepPrefixes?: string[]; + matchDepTypes?: string[]; + matchFileNames?: string[]; + matchManagers?: string[]; + matchNewValue?: string; matchPackageNames?: string[]; matchPackagePatterns?: string[]; matchPackagePrefixes?: string[]; matchRepositories?: string[]; - excludeDepNames?: string[]; - excludeDepPatterns?: string[]; - excludePackageNames?: string[]; - excludePackagePatterns?: string[]; - excludePackagePrefixes?: string[]; - excludeRepositories?: string[]; - matchNewValue?: string; - matchCurrentValue?: string; - matchCurrentVersion?: string; matchSourceUrlPrefixes?: string[]; matchSourceUrls?: string[]; matchUpdateTypes?: UpdateType[]; - matchCategories?: string[]; - matchConfidence?: MergeConfidence[]; registryUrls?: string[] | null; vulnerabilitySeverity?: string; } @@ -390,6 +409,8 @@ export interface RenovateOptionBase { */ globalOnly?: boolean; + inheritConfigSupport?: boolean; + allowedValues?: string[]; allowString?: boolean; @@ -422,6 +443,16 @@ export interface RenovateOptionBase { experimentalIssues?: number[]; advancedUse?: boolean; + + /** + * This is used to add depreciation message in the docs + */ + deprecationMsg?: string; + + /** + * For internal use only: add it to any config option that supports regex or glob matching + */ + patternMatch?: boolean; } export interface RenovateArrayOption< @@ -515,6 +546,8 @@ export interface PackageRuleInputConfig extends Record { releaseTimestamp?: string | null; repository?: string; currentVersionTimestamp?: string; + enabled?: boolean; + skipReason?: SkipReason; } export interface ConfigMigration { diff --git a/lib/config/validation-helpers/regex-glob-matchers.spec.ts b/lib/config/validation-helpers/regex-glob-matchers.spec.ts new file mode 100644 index 00000000000000..570128326ec09c --- /dev/null +++ b/lib/config/validation-helpers/regex-glob-matchers.spec.ts @@ -0,0 +1,33 @@ +import { check } from './regex-glob-matchers'; + +describe('config/validation-helpers/regex-glob-matchers', () => { + it('should error for multiple match alls', () => { + const res = check({ + val: ['*', '**'], + currentPath: 'hostRules[0].allowedHeaders', + }); + expect(res).toHaveLength(1); + }); + + it('should error for invalid regex', () => { + const res = check({ + val: ['[', '/[/', '/.*[/'], + currentPath: 'hostRules[0].allowedHeaders', + }); + expect(res).toHaveLength(2); + }); + + it('should error for non-strings', () => { + const res = check({ + val: ['*', 2], + currentPath: 'hostRules[0].allowedHeaders', + }); + expect(res).toMatchObject([ + { + message: + 'hostRules[0].allowedHeaders: should be an array of strings. You have included object.', + topic: 'Configuration Error', + }, + ]); + }); +}); diff --git a/lib/config/validation-helpers/regex-glob-matchers.ts b/lib/config/validation-helpers/regex-glob-matchers.ts new file mode 100644 index 00000000000000..a1c25cb82f3839 --- /dev/null +++ b/lib/config/validation-helpers/regex-glob-matchers.ts @@ -0,0 +1,44 @@ +import is from '@sindresorhus/is'; +import { getRegexPredicate, isRegexMatch } from '../../util/string-match'; +import type { ValidationMessage } from '../types'; +import type { CheckMatcherArgs } from './types'; + +/** + * Only if type condition or context condition violated then errors array will be mutated to store metadata + */ +export function check({ + val: matchers, + currentPath, +}: CheckMatcherArgs): ValidationMessage[] { + const res: ValidationMessage[] = []; + + if (is.array(matchers, is.string)) { + if ( + (matchers.includes('*') || matchers.includes('**')) && + matchers.length > 1 + ) { + res.push({ + topic: 'Configuration Error', + message: `${currentPath}: Your input contains * or ** along with other patterns. Please remove them, as * or ** matches all patterns.`, + }); + } + for (const matcher of matchers) { + // Validate regex pattern + if (isRegexMatch(matcher)) { + if (!getRegexPredicate(matcher)) { + res.push({ + topic: 'Configuration Error', + message: `Failed to parse regex pattern "${matcher}"`, + }); + } + } + } + } else { + res.push({ + topic: 'Configuration Error', + message: `${currentPath}: should be an array of strings. You have included ${typeof matchers}.`, + }); + } + + return res; +} diff --git a/lib/config/validation-helpers/types.ts b/lib/config/validation-helpers/types.ts index 05f70826cfe420..68e10825820310 100644 --- a/lib/config/validation-helpers/types.ts +++ b/lib/config/validation-helpers/types.ts @@ -4,3 +4,8 @@ export interface CheckManagerArgs { resolvedRule: PackageRule; currentPath: string; } + +export interface CheckMatcherArgs { + val: unknown; + currentPath: string; +} diff --git a/lib/config/validation.spec.ts b/lib/config/validation.spec.ts index 67943b9aa6b52a..f0bfcda1f90fe2 100644 --- a/lib/config/validation.spec.ts +++ b/lib/config/validation.spec.ts @@ -25,7 +25,10 @@ describe('config/validation', () => { const config = { prTitle: 'something', }; - const { warnings } = await configValidation.validateConfig(false, config); + const { warnings } = await configValidation.validateConfig( + 'repo', + config, + ); expect(warnings).toHaveLength(1); expect(warnings).toMatchSnapshot(); }); @@ -35,14 +38,37 @@ describe('config/validation', () => { binarySource: 'something', username: 'user', }; - const { warnings } = await configValidation.validateConfig(false, config); + const { warnings } = await configValidation.validateConfig( + 'repo', + config, + ); + expect(warnings).toHaveLength(2); + expect(warnings).toMatchObject([ + { + message: `The "binarySource" option is a global option reserved only for Renovate's global configuration and cannot be configured within a repository's config file.`, + }, + { + message: `The "username" option is a global option reserved only for Renovate's global configuration and cannot be configured within a repository's config file.`, + }, + ]); + }); + + it('catches global options in inherit config', async () => { + const config = { + binarySource: 'something', + username: 'user', + }; + const { warnings } = await configValidation.validateConfig( + 'inherit', + config, + ); expect(warnings).toHaveLength(2); expect(warnings).toMatchObject([ { - message: `The "binarySource" option is a global option reserved only for Renovate's global configuration and cannot be configured within repository config file.`, + message: `The "binarySource" option is a global option reserved only for Renovate's global configuration and cannot be configured within a repository's config file.`, }, { - message: `The "username" option is a global option reserved only for Renovate's global configuration and cannot be configured within repository config file.`, + message: `The "username" option is a global option reserved only for Renovate's global configuration and cannot be configured within a repository's config file.`, }, ]); }); @@ -57,7 +83,21 @@ describe('config/validation', () => { }, ], }; - const { warnings } = await configValidation.validateConfig(false, config); + const { warnings } = await configValidation.validateConfig( + 'repo', + config, + ); + expect(warnings).toHaveLength(0); + }); + + it('does not warn for valid inheritConfig', async () => { + const config = { + onboarding: false, + }; + const { warnings } = await configValidation.validateConfig( + 'inherit', + config, + ); expect(warnings).toHaveLength(0); }); @@ -65,7 +105,7 @@ describe('config/validation', () => { const config = { commitMessage: '{{{something}}', }; - const { errors } = await configValidation.validateConfig(false, config); + const { errors } = await configValidation.validateConfig('repo', config); expect(errors).toHaveLength(1); expect(errors).toMatchSnapshot(); }); @@ -95,7 +135,7 @@ describe('config/validation', () => { }, ], }; - const { errors } = await configValidation.validateConfig(false, config); + const { errors } = await configValidation.validateConfig('repo', config); expect(errors).toHaveLength(2); expect(errors).toMatchSnapshot(); }); @@ -123,10 +163,15 @@ describe('config/validation', () => { matchCurrentValue: '/^2/i', enabled: true, }, + { + matchPackageNames: ['bad'], + matchNewValue: '/^2(/', + enabled: true, + }, ], }; - const { errors } = await configValidation.validateConfig(false, config); - expect(errors).toHaveLength(2); + const { errors } = await configValidation.validateConfig('repo', config); + expect(errors).toHaveLength(1); }); it('catches invalid matchNewValue', async () => { @@ -152,10 +197,15 @@ describe('config/validation', () => { matchNewValue: '/^2/i', enabled: true, }, + { + matchPackageNames: ['bad'], + matchNewValue: '/^2(/', + enabled: true, + }, ], }; - const { errors } = await configValidation.validateConfig(false, config); - expect(errors).toHaveLength(2); + const { errors } = await configValidation.validateConfig('repo', config); + expect(errors).toHaveLength(1); }); it('catches invalid matchCurrentVersion regex', async () => { @@ -188,7 +238,7 @@ describe('config/validation', () => { }, ], }; - const { errors } = await configValidation.validateConfig(false, config); + const { errors } = await configValidation.validateConfig('repo', config); expect(errors).toHaveLength(2); expect(errors).toMatchSnapshot(); }); @@ -197,25 +247,30 @@ describe('config/validation', () => { const config = { customDatasources: { foo: { + description: 3, randomKey: '', defaultRegistryUrlTemplate: [], transformTemplates: [{}], }, }, } as any; - const { errors } = await configValidation.validateConfig(false, config); + const { errors } = await configValidation.validateConfig('repo', config); expect(errors).toMatchObject([ { message: - 'Invalid `customDatasources.customDatasources.defaultRegistryUrlTemplate` configuration: is a string', + 'Invalid `customDatasources.defaultRegistryUrlTemplate` configuration: is a string', }, { message: - 'Invalid `customDatasources.customDatasources.randomKey` configuration: key is not allowed', + 'Invalid `customDatasources.description` configuration: is not an array of strings', }, { message: - 'Invalid `customDatasources.customDatasources.transformTemplates` configuration: is not an array of string', + 'Invalid `customDatasources.randomKey` configuration: key is not allowed', + }, + { + message: + 'Invalid `customDatasources.transformTemplates` configuration: is not an array of string', }, ]); }); @@ -230,7 +285,7 @@ describe('config/validation', () => { }, }; // @ts-expect-error invalid options - const { errors } = await configValidation.validateConfig(false, config); + const { errors } = await configValidation.validateConfig('repo', config); expect(errors).toMatchObject([ { message: @@ -250,7 +305,7 @@ describe('config/validation', () => { randomKey: '', }, } as any; - const { errors } = await configValidation.validateConfig(false, config); + const { errors } = await configValidation.validateConfig('repo', config); expect(errors).toMatchObject([ { message: @@ -263,7 +318,7 @@ describe('config/validation', () => { const config = { baseBranches: ['/***$}{]][/', '/branch/i'], }; - const { errors } = await configValidation.validateConfig(false, config); + const { errors } = await configValidation.validateConfig('repo', config); expect(errors).toEqual([ { topic: 'Configuration Error', @@ -290,7 +345,7 @@ describe('config/validation', () => { major: null, }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, ); expect(warnings).toHaveLength(0); @@ -308,7 +363,7 @@ describe('config/validation', () => { ], }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, ); expect(warnings).toHaveLength(0); @@ -326,7 +381,7 @@ describe('config/validation', () => { ], }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config as any, ); expect(warnings).toHaveLength(0); @@ -346,7 +401,7 @@ describe('config/validation', () => { ], ])('validates enabled managers for %s', async (_case, config) => { const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, ); expect(warnings).toHaveLength(0); @@ -364,7 +419,7 @@ describe('config/validation', () => { 'errors if included not supported enabled managers for %s', async (_case, config) => { const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, ); expect(warnings).toHaveLength(0); @@ -405,7 +460,7 @@ describe('config/validation', () => { major: null, }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, ); expect(warnings).toHaveLength(1); @@ -434,7 +489,7 @@ describe('config/validation', () => { }, }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, ); expect(warnings).toHaveLength(4); @@ -453,7 +508,7 @@ describe('config/validation', () => { ], }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, true, ); @@ -472,7 +527,7 @@ describe('config/validation', () => { }, }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, ); expect(warnings).toHaveLength(0); @@ -493,7 +548,7 @@ describe('config/validation', () => { ], }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, true, ); @@ -512,7 +567,7 @@ describe('config/validation', () => { ], }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config as any, true, ); @@ -540,7 +595,7 @@ describe('config/validation', () => { ], }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config as any, true, ); @@ -569,7 +624,7 @@ describe('config/validation', () => { ], }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config as any, true, ); @@ -606,7 +661,7 @@ describe('config/validation', () => { ], }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config as RenovateConfig, true, ); @@ -637,7 +692,7 @@ describe('config/validation', () => { ], }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config as any, true, ); @@ -659,7 +714,7 @@ describe('config/validation', () => { ], }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, true, ); @@ -680,7 +735,7 @@ describe('config/validation', () => { ], }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, true, ); @@ -701,10 +756,20 @@ describe('config/validation', () => { extractVersionTemplate: '^(?v\\d+\\.\\d+)', depTypeTemplate: 'apple', }, + { + customType: 'regex', + fileMatch: ['Dockerfile'], + matchStrings: ['ENV (?.*?)\\s'], + packageNameTemplate: 'foo', + datasourceTemplate: 'bar', + registryUrlTemplate: 'foobar', + extractVersionTemplate: '^(?v\\d+\\.\\d+)', + depTypeTemplate: 'apple', + }, ], }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, true, ); @@ -727,7 +792,7 @@ describe('config/validation', () => { ], }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config as any, true, ); @@ -748,7 +813,7 @@ describe('config/validation', () => { ], }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, true, ); @@ -762,7 +827,7 @@ describe('config/validation', () => { $schema: 'renovate.json', }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, true, ); @@ -775,7 +840,7 @@ describe('config/validation', () => { extends: [':timezone', ':timezone(Europe/Berlin)'], }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, true, ); @@ -788,7 +853,7 @@ describe('config/validation', () => { constraints: { packageRules: [{}] }, }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config as never, // TODO: #15963 true, ); @@ -801,7 +866,7 @@ describe('config/validation', () => { prBodyDefinitions: {}, }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, true, ); @@ -817,7 +882,7 @@ describe('config/validation', () => { }, }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, ); expect(warnings).toHaveLength(0); @@ -833,7 +898,7 @@ describe('config/validation', () => { }, }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, ); expect(warnings).toHaveLength(0); @@ -854,7 +919,7 @@ describe('config/validation', () => { }, }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, ); expect(warnings).toHaveLength(0); @@ -887,7 +952,7 @@ describe('config/validation', () => { ], }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, ); expect(errors).toHaveLength(1); @@ -908,7 +973,7 @@ describe('config/validation', () => { }, } as never; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, ); expect(errors).toHaveLength(1); @@ -921,7 +986,7 @@ describe('config/validation', () => { hostType: 'npm', }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, ); expect(errors).toHaveLength(0); @@ -934,7 +999,7 @@ describe('config/validation', () => { extends: ['foo', 'bar', 42] as never, }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, true, ); @@ -949,7 +1014,7 @@ describe('config/validation', () => { ], }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, true, ); @@ -968,7 +1033,7 @@ describe('config/validation', () => { ], } as any; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, true, ); @@ -988,7 +1053,7 @@ describe('config/validation', () => { ], }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, true, ); @@ -1004,11 +1069,14 @@ describe('config/validation', () => { example2: 123, }, }; - const { warnings } = await configValidation.validateConfig(false, config); + const { warnings } = await configValidation.validateConfig( + 'repo', + config, + ); expect(warnings).toMatchObject([ { topic: 'Configuration Error', - message: `The "customEnvVariables" option is a global option reserved only for Renovate's global configuration and cannot be configured within repository config file.`, + message: `The "customEnvVariables" option is a global option reserved only for Renovate's global configuration and cannot be configured within a repository's config file.`, }, ]); }); @@ -1018,7 +1086,7 @@ describe('config/validation', () => { schedule: ['30 5 * * *'], }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, ); expect(warnings).toHaveLength(0); @@ -1046,7 +1114,7 @@ describe('config/validation', () => { ], }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, ); expect(warnings).toHaveLength(0); @@ -1073,7 +1141,7 @@ describe('config/validation', () => { ], }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, ); expect(warnings).toHaveLength(0); @@ -1100,7 +1168,7 @@ describe('config/validation', () => { ], }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', config, ); expect(warnings).toHaveLength(0); @@ -1123,7 +1191,7 @@ describe('config/validation', () => { }, }; const { errors, warnings } = await configValidation.validateConfig( - false, + 'repo', // @ts-expect-error: testing invalid values in env object config, ); @@ -1159,7 +1227,7 @@ describe('config/validation', () => { ], }; const { errors, warnings } = await configValidation.validateConfig( - false, + 'repo', config, ); expect(errors).toMatchObject([ @@ -1177,6 +1245,34 @@ describe('config/validation', () => { expect(warnings).toHaveLength(0); expect(errors).toHaveLength(2); }); + + it('catches when * or ** is combined with others patterns in a regexOrGlob option', async () => { + const config = { + packageRules: [ + { + matchRepositories: ['groupA/**', 'groupB/**'], // valid + enabled: false, + }, + { + matchRepositories: ['*', 'repo'], // invalid + enabled: true, + }, + ], + }; + const { errors, warnings } = await configValidation.validateConfig( + 'repo', + config, + ); + expect(errors).toMatchObject([ + { + message: + 'packageRules[1].matchRepositories: Your input contains * or ** along with other patterns. Please remove them, as * or ** matches all patterns.', + topic: 'Configuration Error', + }, + ]); + expect(errors).toHaveLength(1); + expect(warnings).toHaveLength(0); + }); }); describe('validateConfig() -> globaOnly options', () => { @@ -1193,7 +1289,7 @@ describe('config/validation', () => { allowedHeaders: ['X-Auth-Token'], }; const { warnings, errors } = await configValidation.validateConfig( - true, + 'global', config, ); expect(warnings).toHaveLength(0); @@ -1211,7 +1307,10 @@ describe('config/validation', () => { }, ], }; - const { errors } = await configValidation.validateConfig(true, config); + const { errors } = await configValidation.validateConfig( + 'global', + config, + ); expect(errors).toMatchObject([ { message: @@ -1229,7 +1328,7 @@ describe('config/validation', () => { allowedEnv: ['SOME*'], }; const { warnings, errors } = await configValidation.validateConfig( - true, + 'global', config, ); expect(warnings).toHaveLength(0); @@ -1242,7 +1341,10 @@ describe('config/validation', () => { SOME_VAR: 'SOME_VALUE', }, }; - const { errors } = await configValidation.validateConfig(true, config); + const { errors } = await configValidation.validateConfig( + 'global', + config, + ); expect(errors).toMatchObject([ { message: @@ -1277,7 +1379,7 @@ describe('config/validation', () => { reviewersSampleSize: null, }; const { warnings, errors } = await configValidation.validateConfig( - false, + 'repo', // @ts-expect-error: contains invalid values config, ); @@ -1293,7 +1395,7 @@ describe('config/validation', () => { binarySource: 'invalid' as never, }; const { warnings } = await configValidation.validateConfig( - true, + 'global', config, ); expect(warnings).toEqual([ @@ -1310,7 +1412,7 @@ describe('config/validation', () => { baseDir: false as never, }; const { warnings } = await configValidation.validateConfig( - true, + 'global', config, ); expect(warnings).toEqual([ @@ -1326,7 +1428,7 @@ describe('config/validation', () => { requireConfig: 'invalid' as never, }; const { warnings } = await configValidation.validateConfig( - true, + 'global', config, ); expect(warnings).toEqual([ @@ -1343,7 +1445,7 @@ describe('config/validation', () => { dryRun: 'invalid' as never, }; const { warnings } = await configValidation.validateConfig( - true, + 'global', config, ); expect(warnings).toEqual([ @@ -1360,7 +1462,7 @@ describe('config/validation', () => { repositoryCache: 'invalid' as never, }; const { warnings } = await configValidation.validateConfig( - true, + 'global', config, ); expect(warnings).toEqual([ @@ -1377,7 +1479,7 @@ describe('config/validation', () => { onboardingConfigFileName: 'invalid' as never, }; const { warnings } = await configValidation.validateConfig( - true, + 'global', config, ); expect(warnings).toEqual([ @@ -1397,7 +1499,7 @@ describe('config/validation', () => { }, }; const { warnings } = await configValidation.validateConfig( - true, + 'global', config, ); expect(warnings).toEqual([ @@ -1408,7 +1510,7 @@ describe('config/validation', () => { }, { topic: 'Configuration Error', - message: `The "binarySource" option is a global option reserved only for Renovate's global configuration and cannot be configured within repository config file.`, + message: `The "binarySource" option is a global option reserved only for Renovate's global configuration and cannot be configured within a repository's config file.`, }, ]); }); @@ -1425,7 +1527,7 @@ describe('config/validation', () => { }, }; const { warnings } = await configValidation.validateConfig( - true, + 'global', config, ); expect(warnings).toEqual([ @@ -1442,7 +1544,7 @@ describe('config/validation', () => { gitUrl: 'invalid' as never, }; const { warnings } = await configValidation.validateConfig( - true, + 'global', config, ); expect(warnings).toEqual([ @@ -1460,7 +1562,10 @@ describe('config/validation', () => { unicodeEmoji: false, detectGlobalManagerConfig: 'invalid-type', }; - const { warnings } = await configValidation.validateConfig(true, config); + const { warnings } = await configValidation.validateConfig( + 'global', + config, + ); expect(warnings).toMatchObject([ { message: `Configuration option \`detectGlobalManagerConfig\` should be a boolean. Found: ${JSON.stringify( @@ -1476,7 +1581,10 @@ describe('config/validation', () => { prCommitsPerRunLimit: 2, gitTimeout: 'invalid-type', }; - const { warnings } = await configValidation.validateConfig(true, config); + const { warnings } = await configValidation.validateConfig( + 'global', + config, + ); expect(warnings).toMatchObject([ { message: `Configuration option \`gitTimeout\` should be an integer. Found: ${JSON.stringify( @@ -1492,9 +1600,10 @@ describe('config/validation', () => { allowedPostUpgradeCommands: ['cmd'], checkedBranches: 'invalid-type', gitNoVerify: ['invalid'], + mergeConfidenceDatasources: [1], }; const { warnings } = await configValidation.validateConfig( - true, + 'global', // @ts-expect-error: contains invalid values config, ); @@ -1504,6 +1613,11 @@ describe('config/validation', () => { 'Configuration option `checkedBranches` should be a list (Array).', topic: 'Configuration Error', }, + { + topic: 'Configuration Error', + message: + 'Invalid value `1` for `mergeConfidenceDatasources`. The allowed values are go, maven, npm, nuget, packagist, pypi, rubygems.', + }, { message: 'Invalid value for `gitNoVerify`. The allowed values are commit, push.', @@ -1525,7 +1639,7 @@ describe('config/validation', () => { }, }; const { warnings } = await configValidation.validateConfig( - true, + 'global', // @ts-expect-error: contains invalid values config, ); @@ -1549,7 +1663,10 @@ describe('config/validation', () => { example2: 123, }, }; - const { warnings } = await configValidation.validateConfig(true, config); + const { warnings } = await configValidation.validateConfig( + 'global', + config, + ); expect(warnings).toMatchObject([ { message: @@ -1567,7 +1684,7 @@ describe('config/validation', () => { }, }; const { warnings, errors } = await configValidation.validateConfig( - true, + 'global', config, ); expect(warnings).toHaveLength(0); @@ -1586,12 +1703,102 @@ describe('config/validation', () => { autodiscoverTopics: null, }; const { warnings, errors } = await configValidation.validateConfig( - true, + 'global', // @ts-expect-error: contains invalid values config, ); expect(warnings).toHaveLength(0); expect(errors).toHaveLength(0); }); + + it('fails for missing reportPath if reportType is "s3"', async () => { + const config: RenovateConfig = { + reportType: 's3', + }; + const { warnings, errors } = await configValidation.validateConfig( + 'global', + config, + ); + expect(warnings).toHaveLength(0); + expect(errors).toHaveLength(1); + }); + + it('validates reportPath if reportType is "s3"', async () => { + const config: RenovateConfig = { + reportType: 's3', + reportPath: 's3://bucket-name/key-name', + }; + const { warnings, errors } = await configValidation.validateConfig( + 'global', + config, + ); + expect(warnings).toHaveLength(0); + expect(errors).toHaveLength(0); + }); + + it('fails for missing reportPath if reportType is "file"', async () => { + const config: RenovateConfig = { + reportType: 'file', + }; + const { warnings, errors } = await configValidation.validateConfig( + 'global', + config, + ); + expect(warnings).toHaveLength(0); + expect(errors).toHaveLength(1); + }); + + it('validates reportPath if reportType is "file"', async () => { + const config: RenovateConfig = { + reportType: 'file', + reportPath: './report.json', + }; + const { warnings, errors } = await configValidation.validateConfig( + 'global', + config, + ); + expect(warnings).toHaveLength(0); + expect(errors).toHaveLength(0); + }); + + it('catches when * or ** is combined with others patterns in a regexOrGlob option', async () => { + const config = { + packageRules: [ + { + matchRepositories: ['*', 'repo'], // invalid + enabled: false, + }, + ], + allowedHeaders: ['*', '**'], // invalid + autodiscoverProjects: ['**', 'project'], // invalid + allowedEnv: ['env_var'], // valid + }; + const { errors, warnings } = await configValidation.validateConfig( + 'global', + config, + ); + expect(warnings).toMatchObject([ + { + message: + 'allowedHeaders: Your input contains * or ** along with other patterns. Please remove them, as * or ** matches all patterns.', + topic: 'Configuration Error', + }, + { + message: + 'autodiscoverProjects: Your input contains * or ** along with other patterns. Please remove them, as * or ** matches all patterns.', + topic: 'Configuration Error', + }, + ]); + + expect(errors).toMatchObject([ + { + message: + 'packageRules[0].matchRepositories: Your input contains * or ** along with other patterns. Please remove them, as * or ** matches all patterns.', + topic: 'Configuration Error', + }, + ]); + expect(warnings).toHaveLength(2); + expect(errors).toHaveLength(1); + }); }); }); diff --git a/lib/config/validation.ts b/lib/config/validation.ts index 1a5b269cda6671..f7050c2ac7925f 100644 --- a/lib/config/validation.ts +++ b/lib/config/validation.ts @@ -24,6 +24,7 @@ import { GlobalConfig } from './global'; import { migrateConfig } from './migration'; import { getOptions } from './options'; import { resolveConfigPresets } from './presets'; +import { supportedDatasources } from './presets/internal/merge-confidence'; import { AllowedParents, type RenovateConfig, @@ -34,12 +35,16 @@ import { allowedStatusCheckStrings, } from './types'; import * as managerValidator from './validation-helpers/managers'; +import * as regexOrGlobValidator from './validation-helpers/regex-glob-matchers'; const options = getOptions(); +let optionsInitialized = false; let optionTypes: Record; let optionParents: Record; let optionGlobals: Set; +let optionInherits: Set; +let optionRegexOrGlob: Set; const managerList = getManagerList(); @@ -98,16 +103,50 @@ function getDeprecationMessage(option: string): string | undefined { return deprecatedOptions[option]; } +function isInhertConfigOption(key: string): boolean { + return optionInherits.has(key); +} + +function isRegexOrGlobOption(key: string): boolean { + return optionRegexOrGlob.has(key); +} + function isGlobalOption(key: string): boolean { - if (!optionGlobals) { - optionGlobals = new Set(); - for (const option of options) { - if (option.globalOnly) { - optionGlobals.add(option.name); - } + return optionGlobals.has(key); +} + +function initOptions(): void { + if (optionsInitialized) { + return; + } + + optionParents = {}; + optionInherits = new Set(); + optionTypes = {}; + optionRegexOrGlob = new Set(); + optionGlobals = new Set(); + + for (const option of options) { + optionTypes[option.name] = option.type; + + if (option.parents) { + optionParents[option.name] = option.parents; + } + + if (option.inheritConfigSupport) { + optionInherits.add(option.name); + } + + if (option.patternMatch) { + optionRegexOrGlob.add(option.name); + } + + if (option.globalOnly) { + optionGlobals.add(option.name); } } - return optionGlobals.has(key); + + optionsInitialized = true; } export function getParentName(parentPath: string | undefined): string { @@ -121,25 +160,13 @@ export function getParentName(parentPath: string | undefined): string { } export async function validateConfig( - isGlobalConfig: boolean, + configType: 'global' | 'inherit' | 'repo', config: RenovateConfig, isPreset?: boolean, parentPath?: string, ): Promise { - if (!optionTypes) { - optionTypes = {}; - options.forEach((option) => { - optionTypes[option.name] = option.type; - }); - } - if (!optionParents) { - optionParents = {}; - options.forEach((option) => { - if (option.parents) { - optionParents[option.name] = option.parents; - } - }); - } + initOptions(); + let errors: ValidationMessage[] = []; let warnings: ValidationMessage[] = []; @@ -164,20 +191,25 @@ export async function validateConfig( }); } - if (isGlobalConfig && isGlobalOption(key)) { - await validateGlobalConfig( - key, - val, - optionTypes[key], - warnings, - currentPath, - ); - continue; - } else { - if (isGlobalOption(key) && !isFalseGlobal(key, parentPath)) { + if (isGlobalOption(key)) { + if (configType === 'global') { + await validateGlobalConfig( + key, + val, + optionTypes[key], + warnings, + errors, + currentPath, + config, + ); + continue; + } else if ( + !isFalseGlobal(key, parentPath) && + !(configType === 'inherit' && isInhertConfigOption(key)) + ) { warnings.push({ topic: 'Configuration Error', - message: `The "${key}" option is a global option reserved only for Renovate's global configuration and cannot be configured within repository config file.`, + message: `The "${key}" option is a global option reserved only for Renovate's global configuration and cannot be configured within a repository's config file.`, }); continue; } @@ -267,7 +299,12 @@ export async function validateConfig( }); } } else if ( - ['allowedVersions', 'matchCurrentVersion'].includes(key) && + [ + 'allowedVersions', + 'matchCurrentVersion', + 'matchCurrentValue', + 'matchNewValue', + ].includes(key) && isRegexMatch(val) ) { if (!getRegexPredicate(val)) { @@ -276,24 +313,6 @@ export async function validateConfig( message: `Invalid regExp for ${currentPath}: \`${val}\``, }); } - } else if ( - key === 'matchCurrentValue' && - is.string(val) && - !getRegexPredicate(val) - ) { - errors.push({ - topic: 'Configuration Error', - message: `Invalid regExp for ${currentPath}: \`${val}\``, - }); - } else if ( - key === 'matchNewValue' && - is.string(val) && - !getRegexPredicate(val) - ) { - errors.push({ - topic: 'Configuration Error', - message: `Invalid regExp for ${currentPath}: \`${val}\``, - }); } else if (key === 'timezone' && val !== null) { const [validTimezone, errorMessage] = hasValidTimezone(val as string); if (!validTimezone) { @@ -327,7 +346,7 @@ export async function validateConfig( for (const [subIndex, subval] of val.entries()) { if (is.object(subval)) { const subValidation = await validateConfig( - isGlobalConfig, + configType, subval as RenovateConfig, isPreset, `${currentPath}[${subIndex}]`, @@ -336,6 +355,14 @@ export async function validateConfig( errors = errors.concat(subValidation.errors); } } + if (isRegexOrGlobOption(key)) { + errors.push( + ...regexOrGlobValidator.check({ + val, + currentPath, + }), + ); + } if (key === 'extends') { for (const subval of val) { if (is.string(subval)) { @@ -378,11 +405,13 @@ export async function validateConfig( 'matchDepTypes', 'matchDepNames', 'matchDepPatterns', + 'matchDepPrefixes', 'matchPackageNames', 'matchPackagePatterns', 'matchPackagePrefixes', 'excludeDepNames', 'excludeDepPatterns', + 'excludeDepPrefixes', 'excludePackageNames', 'excludePackagePatterns', 'excludePackagePrefixes', @@ -446,6 +475,7 @@ export async function validateConfig( 'separateMajorMinor', 'separateMinorPatch', 'separateMultipleMajor', + 'separateMultipleMinor', 'versioning', ]; if (is.nonEmptyArray(resolvedRule.matchUpdateTypes)) { @@ -627,9 +657,10 @@ export async function validateConfig( }); } } else if (key === 'env') { - const allowedEnvVars = isGlobalConfig - ? (config.allowedEnv as string[]) ?? [] - : GlobalConfig.get('allowedEnv', []); + const allowedEnvVars = + configType === 'global' + ? (config.allowedEnv as string[]) ?? [] + : GlobalConfig.get('allowedEnv', []); for (const [envVarName, envVarValue] of Object.entries(val)) { if (!is.string(envVarValue)) { errors.push({ @@ -692,19 +723,28 @@ export async function validateConfig( if (!allowedKeys.includes(subKey)) { errors.push({ topic: 'Configuration Error', - message: `Invalid \`${currentPath}.${key}.${subKey}\` configuration: key is not allowed`, + message: `Invalid \`${currentPath}.${subKey}\` configuration: key is not allowed`, }); } else if (subKey === 'transformTemplates') { if (!is.array(subValue, is.string)) { errors.push({ topic: 'Configuration Error', - message: `Invalid \`${currentPath}.${key}.${subKey}\` configuration: is not an array of string`, + message: `Invalid \`${currentPath}.${subKey}\` configuration: is not an array of string`, + }); + } + } else if (subKey === 'description') { + if ( + !(is.string(subValue) || is.array(subValue, is.string)) + ) { + errors.push({ + topic: 'Configuration Error', + message: `Invalid \`${currentPath}.${subKey}\` configuration: is not an array of strings`, }); } } else if (!is.string(subValue)) { errors.push({ topic: 'Configuration Error', - message: `Invalid \`${currentPath}.${key}.${subKey}\` configuration: is a string`, + message: `Invalid \`${currentPath}.${subKey}\` configuration: is a string`, }); } } @@ -715,7 +755,7 @@ export async function validateConfig( .map((option) => option.name); if (!ignoredObjects.includes(key)) { const subValidation = await validateConfig( - isGlobalConfig, + configType, val, isPreset, currentPath, @@ -735,9 +775,10 @@ export async function validateConfig( } if (key === 'hostRules' && is.array(val)) { - const allowedHeaders = isGlobalConfig - ? (config.allowedHeaders as string[]) ?? [] - : GlobalConfig.get('allowedHeaders', []); + const allowedHeaders = + configType === 'global' + ? (config.allowedHeaders as string[]) ?? [] + : GlobalConfig.get('allowedHeaders', []); for (const rule of val as HostRule[]) { if (!rule.headers) { continue; @@ -774,6 +815,19 @@ export async function validateConfig( return { errors, warnings }; } +function hasField( + customManager: Partial, + field: string, +): boolean { + const templateField = `${field}Template` as keyof RegexManagerTemplates; + return !!( + customManager[templateField] ?? + customManager.matchStrings?.some((matchString) => + matchString.includes(`(?<${field}>`), + ) + ); +} + function validateRegexManagerFields( customManager: Partial, currentPath: string, @@ -801,21 +855,23 @@ function validateRegexManagerFields( }); } - const mandatoryFields = ['depName', 'currentValue', 'datasource']; + const mandatoryFields = ['currentValue', 'datasource']; for (const field of mandatoryFields) { - const templateField = `${field}Template` as keyof RegexManagerTemplates; - if ( - !customManager[templateField] && - !customManager.matchStrings?.some((matchString) => - matchString.includes(`(?<${field}>`), - ) - ) { + if (!hasField(customManager, field)) { errors.push({ topic: 'Configuration Error', message: `Regex Managers must contain ${field}Template configuration or regex group named ${field}`, }); } } + + const nameFields = ['depName', 'packageName']; + if (!nameFields.some((field) => hasField(customManager, field))) { + errors.push({ + topic: 'Configuration Error', + message: `Regex Managers must contain depName or packageName regex groups or templates`, + }); + } } /** @@ -826,7 +882,9 @@ async function validateGlobalConfig( val: unknown, type: string, warnings: ValidationMessage[], + errors: ValidationMessage[], currentPath: string | undefined, + config: RenovateConfig, ): Promise { if (val !== null) { if (type === 'string') { @@ -880,6 +938,17 @@ async function validateGlobalConfig( message: `Invalid value \`${val}\` for \`${currentPath}\`. The allowed values are ${['default', 'ssh', 'endpoint'].join(', ')}.`, }); } + + if ( + key === 'reportType' && + ['s3', 'file'].includes(val) && + !is.string(config.reportPath) + ) { + errors.push({ + topic: 'Configuration Error', + message: `reportType '${val}' requires a configured reportPath`, + }); + } } else { warnings.push({ topic: 'Configuration Error', @@ -906,6 +975,14 @@ async function validateGlobalConfig( } } else if (type === 'array') { if (is.array(val)) { + if (isRegexOrGlobOption(key)) { + warnings.push( + ...regexOrGlobValidator.check({ + val, + currentPath: currentPath!, + }), + ); + } if (key === 'gitNoVerify') { const allowedValues = ['commit', 'push']; for (const value of val as string[]) { @@ -917,6 +994,17 @@ async function validateGlobalConfig( } } } + if (key === 'mergeConfidenceDatasources') { + const allowedValues = supportedDatasources; + for (const value of val as string[]) { + if (!allowedValues.includes(value)) { + warnings.push({ + topic: 'Configuration Error', + message: `Invalid value \`${value}\` for \`${currentPath}\`. The allowed values are ${allowedValues.join(', ')}.`, + }); + } + } + } } else { warnings.push({ topic: 'Configuration Error', @@ -926,14 +1014,14 @@ async function validateGlobalConfig( } else if (type === 'object') { if (is.plainObject(val)) { if (key === 'onboardingConfig') { - const subValidation = await validateConfig(false, val); + const subValidation = await validateConfig('repo', val); for (const warning of subValidation.warnings.concat( subValidation.errors, )) { warnings.push(warning); } } else if (key === 'force') { - const subValidation = await validateConfig(true, val); + const subValidation = await validateConfig('global', val); for (const warning of subValidation.warnings.concat( subValidation.errors, )) { diff --git a/lib/constants/error-messages.ts b/lib/constants/error-messages.ts index 95daa453ee8ecb..434c39926f9d10 100644 --- a/lib/constants/error-messages.ts +++ b/lib/constants/error-messages.ts @@ -17,6 +17,8 @@ export const CONFIG_PRESETS_INVALID = 'config-presets-invalid'; export const CONFIG_SECRETS_EXPOSED = 'config-secrets-exposed'; export const CONFIG_SECRETS_INVALID = 'config-secrets-invalid'; export const CONFIG_GIT_URL_UNAVAILABLE = 'config-git-url-unavailable'; +export const CONFIG_INHERIT_NOT_FOUND = 'config-inherit-not-found'; +export const CONFIG_INHERIT_PARSE_ERROR = 'config-inherit-parse-error'; // Repository Errors - causes repo to be considered as disabled export const REPOSITORY_ACCESS_FORBIDDEN = 'forbidden'; @@ -28,6 +30,8 @@ export const REPOSITORY_CLOSED_ONBOARDING = 'disabled-closed-onboarding'; export const REPOSITORY_DISABLED_BY_CONFIG = 'disabled-by-config'; export const REPOSITORY_NO_CONFIG = 'disabled-no-config'; export const REPOSITORY_EMPTY = 'empty'; +export const REPOSITORY_FORK_MISSING = 'fork-missing'; +export const REPOSITORY_FORK_MODE_FORKED = 'fork-mode-forked'; export const REPOSITORY_FORKED = 'fork'; export const REPOSITORY_MIRRORED = 'mirror'; export const REPOSITORY_NOT_FOUND = 'not-found'; diff --git a/lib/constants/platforms.ts b/lib/constants/platforms.ts index d1ee8156312ba6..65d5c60a7e931d 100644 --- a/lib/constants/platforms.ts +++ b/lib/constants/platforms.ts @@ -14,7 +14,6 @@ export const GITEA_API_USING_HOST_TYPES = [ 'gitea-changelog', 'gitea-releases', 'gitea-tags', - 'custom', ]; export const GITHUB_API_USING_HOST_TYPES = [ @@ -26,7 +25,6 @@ export const GITHUB_API_USING_HOST_TYPES = [ 'hermit', 'github-changelog', 'conan', - 'custom', ]; export const GITLAB_API_USING_HOST_TYPES = [ @@ -36,12 +34,10 @@ export const GITLAB_API_USING_HOST_TYPES = [ 'gitlab-packages', 'gitlab-changelog', 'pypi', - 'custom', ]; export const BITBUCKET_API_USING_HOST_TYPES = [ 'bitbucket', 'bitbucket-changelog', 'bitbucket-tags', - 'custom', ]; diff --git a/lib/expose.cjs b/lib/expose.cjs index b5ee8169d57ba1..c2d3c5d22e6002 100644 --- a/lib/expose.cjs +++ b/lib/expose.cjs @@ -22,4 +22,26 @@ function prettier() { return require('prettier'); } -module.exports = { re2, pkg, prettier }; +/** + * return's openpgp + * @returns {typeof import('openpgp')} + */ +function openpgp() { + return require('openpgp'); +} + +/** + * return's sqlite + * @returns {typeof import('better-sqlite3')} + */ +function sqlite() { + return require('better-sqlite3'); +} + +module.exports = { + re2, + pkg, + openpgp, + prettier, + sqlite, +}; diff --git a/lib/instrumentation/__mocks__/index.ts b/lib/instrumentation/__mocks__/index.ts deleted file mode 100644 index be8ac341cd32d3..00000000000000 --- a/lib/instrumentation/__mocks__/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { NoopTracer } from '@opentelemetry/api/build/src/trace/NoopTracer'; -import { NoopTracerProvider } from '@opentelemetry/api/build/src/trace/NoopTracerProvider'; - -export const getTracerProvider = jest.fn(args => new NoopTracerProvider()); -export const getTracer = jest.fn(args => new NoopTracer()); diff --git a/lib/instrumentation/index.spec.ts b/lib/instrumentation/index.spec.ts index ddb347e0155c6b..6e3927311bdcef 100644 --- a/lib/instrumentation/index.spec.ts +++ b/lib/instrumentation/index.spec.ts @@ -1,6 +1,5 @@ import { ProxyTracerProvider } from '@opentelemetry/api'; import * as api from '@opentelemetry/api'; -import { NoopTracerProvider } from '@opentelemetry/api/build/src/trace/NoopTracerProvider'; import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; import { disableInstrumentations, @@ -28,7 +27,7 @@ describe('instrumentation/index', () => { const traceProvider = getTracerProvider(); expect(traceProvider).toBeInstanceOf(ProxyTracerProvider); const provider = traceProvider as ProxyTracerProvider; - expect(provider.getDelegate()).toBeInstanceOf(NoopTracerProvider); + expect(provider.constructor.name).toBe('ProxyTracerProvider'); }); it('activate console logger', () => { diff --git a/lib/instrumentation/reporting.spec.ts b/lib/instrumentation/reporting.spec.ts new file mode 100644 index 00000000000000..494e4fd203b0d5 --- /dev/null +++ b/lib/instrumentation/reporting.spec.ts @@ -0,0 +1,227 @@ +import type { S3Client } from '@aws-sdk/client-s3'; +import { mockDeep } from 'jest-mock-extended'; +import { s3 } from '../../test/s3'; +import { fs, logger } from '../../test/util'; +import type { RenovateConfig } from '../config/types'; +import type { PackageFile } from '../modules/manager/types'; +import type { BranchCache } from '../util/cache/repository/types'; +import { + addBranchStats, + addExtractionStats, + exportStats, + finalizeReport, + getReport, +} from './reporting'; + +jest.mock('../util/fs', () => mockDeep()); +jest.mock('../util/s3', () => mockDeep()); +jest.mock('../logger', () => mockDeep()); + +describe('instrumentation/reporting', () => { + const branchInformation: Partial[] = [ + { + branchName: 'a-branch-name', + prNo: 20, + upgrades: [ + { + currentVersion: '21.1.1', + currentValue: 'v21.1.1', + newVersion: '22.0.0', + newValue: 'v22.0.0', + }, + ], + }, + ]; + const packageFiles: Record = { + terraform: [ + { + packageFile: 'terraform/versions.tf', + deps: [ + { + currentValue: 'v21.1.1', + currentVersion: '4.4.3', + updates: [ + { + bucket: 'non-major', + newVersion: '4.7.0', + newValue: '~> 4.7.0', + }, + ], + }, + ], + }, + ], + }; + + const expectedReport = { + problems: [], + repositories: { + 'myOrg/myRepo': { + problems: [], + branches: branchInformation, + packageFiles, + }, + }, + }; + + it('return empty report if no stats have been added', () => { + const config = {}; + addBranchStats(config, []); + addExtractionStats(config, { + branchList: [], + branches: [], + packageFiles: {}, + }); + + expect(getReport()).toEqual({ + problems: [], + repositories: {}, + }); + }); + + it('return report if reportType is set to logging', () => { + const config: RenovateConfig = { + repository: 'myOrg/myRepo', + reportType: 'logging', + }; + + addBranchStats(config, branchInformation); + addExtractionStats(config, { branchList: [], branches: [], packageFiles }); + + expect(getReport()).toEqual(expectedReport); + }); + + it('log report if reportType is set to logging', async () => { + const config: RenovateConfig = { + repository: 'myOrg/myRepo', + reportType: 'logging', + }; + + addBranchStats(config, branchInformation); + addExtractionStats(config, { branchList: [], branches: [], packageFiles }); + + await exportStats(config); + expect(logger.logger.info).toHaveBeenCalledWith( + { report: expectedReport }, + 'Printing report', + ); + }); + + it('write report if reportType is set to file', async () => { + const config: RenovateConfig = { + repository: 'myOrg/myRepo', + reportType: 'file', + reportPath: './report.json', + }; + + addBranchStats(config, branchInformation); + addExtractionStats(config, { branchList: [], branches: [], packageFiles }); + + await exportStats(config); + expect(fs.writeSystemFile).toHaveBeenCalledWith( + config.reportPath, + JSON.stringify(expectedReport), + ); + }); + + it('send report to an S3 bucket if reportType is s3', async () => { + const mockClient = mockDeep(); + s3.parseS3Url.mockReturnValue({ Bucket: 'bucket-name', Key: 'key-name' }); + // @ts-expect-error TS2589 + s3.getS3Client.mockReturnValue(mockClient); + + const config: RenovateConfig = { + repository: 'myOrg/myRepo', + reportType: 's3', + reportPath: 's3://bucket-name/key-name', + }; + + addBranchStats(config, branchInformation); + addExtractionStats(config, { branchList: [], branches: [], packageFiles }); + + await exportStats(config); + expect(mockClient.send.mock.calls[0][0]).toMatchObject({ + input: { + Body: JSON.stringify(expectedReport), + }, + }); + }); + + it('handle failed parsing of S3 url', async () => { + s3.parseS3Url.mockReturnValue(null); + + const config: RenovateConfig = { + repository: 'myOrg/myRepo', + reportType: 's3', + reportPath: 'aPath', + }; + + addBranchStats(config, branchInformation); + addExtractionStats(config, { branchList: [], branches: [], packageFiles }); + + await exportStats(config); + expect(logger.logger.warn).toHaveBeenCalledWith( + { reportPath: config.reportPath }, + 'Failed to parse s3 URL', + ); + }); + + it('catch exception', async () => { + const config: RenovateConfig = { + repository: 'myOrg/myRepo', + reportType: 'file', + reportPath: './report.json', + }; + + addBranchStats(config, branchInformation); + addExtractionStats(config, { branchList: [], branches: [], packageFiles }); + + fs.writeSystemFile.mockRejectedValue(null); + await expect(exportStats(config)).toResolve(); + }); + + it('should add problems to report', () => { + const config: RenovateConfig = { + repository: 'myOrg/myRepo', + reportType: 'logging', + }; + const expectedReport = { + problems: [ + { + level: 30, + msg: 'a root problem', + }, + ], + repositories: { + 'myOrg/myRepo': { + problems: [ + { + level: 30, + msg: 'a repo problem', + }, + ], + branches: branchInformation, + packageFiles, + }, + }, + }; + + addBranchStats(config, branchInformation); + addExtractionStats(config, { branchList: [], branches: [], packageFiles }); + + logger.getProblems.mockReturnValue([ + { + repository: 'myOrg/myRepo', + level: 30, + msg: 'a repo problem', + }, + { + level: 30, + msg: 'a root problem', + }, + ]); + finalizeReport(); + + expect(getReport()).toEqual(expectedReport); + }); +}); diff --git a/lib/instrumentation/reporting.ts b/lib/instrumentation/reporting.ts new file mode 100644 index 00000000000000..8b422f3d3d9b41 --- /dev/null +++ b/lib/instrumentation/reporting.ts @@ -0,0 +1,115 @@ +import { PutObjectCommand, PutObjectCommandInput } from '@aws-sdk/client-s3'; +import is from '@sindresorhus/is'; +import type { RenovateConfig } from '../config/types'; +import { getProblems, logger } from '../logger'; +import type { BranchCache } from '../util/cache/repository/types'; +import { writeSystemFile } from '../util/fs'; +import { getS3Client, parseS3Url } from '../util/s3'; +import type { ExtractResult } from '../workers/repository/process/extract-update'; +import type { Report } from './types'; + +const report: Report = { + problems: [], + repositories: {}, +}; + +export function addBranchStats( + config: RenovateConfig, + branchesInformation: Partial[], +): void { + if (is.nullOrUndefined(config.reportType)) { + return; + } + + coerceRepo(config.repository!); + report.repositories[config.repository!].branches = branchesInformation; +} + +export function addExtractionStats( + config: RenovateConfig, + extractResult: ExtractResult, +): void { + if (is.nullOrUndefined(config.reportType)) { + return; + } + + coerceRepo(config.repository!); + report.repositories[config.repository!].packageFiles = + extractResult.packageFiles; +} + +export function finalizeReport(): void { + const allProblems = structuredClone(getProblems()); + for (const problem of allProblems) { + const repository = problem.repository; + delete problem.repository; + + // if the problem can be connected to a repository add it their else add to the root list + if (repository) { + coerceRepo(repository); + report.repositories[repository].problems.push(problem); + } else { + report.problems.push(problem); + } + } +} + +export async function exportStats(config: RenovateConfig): Promise { + try { + if (is.nullOrUndefined(config.reportType)) { + return; + } + + if (config.reportType === 'logging') { + logger.info({ report }, 'Printing report'); + return; + } + + if (config.reportType === 'file') { + const path = config.reportPath!; + await writeSystemFile(path, JSON.stringify(report)); + logger.debug({ path }, 'Writing report'); + return; + } + + if (config.reportType === 's3') { + const s3Url = parseS3Url(config.reportPath!); + if (is.nullOrUndefined(s3Url)) { + logger.warn( + { reportPath: config.reportPath }, + 'Failed to parse s3 URL', + ); + return; + } + + const s3Params: PutObjectCommandInput = { + Bucket: s3Url.Bucket, + Key: s3Url.Key, + Body: JSON.stringify(report), + ContentType: 'application/json', + }; + + const client = getS3Client(); + const command = new PutObjectCommand(s3Params); + await client.send(command); + } + } catch (err) { + logger.warn({ err }, 'Reporting.exportStats() - failure'); + } +} + +export function getReport(): Report { + return structuredClone(report); +} + +function coerceRepo(repository: string): void { + if (!is.undefined(report.repositories[repository])) { + return; + } + + report.repositories[repository] = { + problems: [], + branches: [], + packageFiles: {}, + }; +} diff --git a/lib/instrumentation/types.ts b/lib/instrumentation/types.ts index a753ecb56d1436..d4e86c1c7ea6cc 100644 --- a/lib/instrumentation/types.ts +++ b/lib/instrumentation/types.ts @@ -1,4 +1,7 @@ import type { Attributes, SpanKind } from '@opentelemetry/api'; +import type { BunyanRecord } from '../logger/types'; +import type { PackageFile } from '../modules/manager/types'; +import type { BranchCache } from '../util/cache/repository/types'; /** * The instrumentation decorator parameters. @@ -24,3 +27,14 @@ export interface SpanParameters { */ kind?: SpanKind; } + +export interface Report { + problems: BunyanRecord[]; + repositories: Record; +} + +interface RepoReport { + problems: BunyanRecord[]; + branches: Partial[]; + packageFiles: Record; +} diff --git a/lib/modules/datasource/__snapshots__/metadata.spec.ts.snap b/lib/modules/datasource/__snapshots__/metadata.spec.ts.snap index 74602e1015d630..3954823e05f0fb 100644 --- a/lib/modules/datasource/__snapshots__/metadata.spec.ts.snap +++ b/lib/modules/datasource/__snapshots__/metadata.spec.ts.snap @@ -34,7 +34,7 @@ exports[`modules/datasource/metadata Should handle failed parsing of sourceUrls exports[`modules/datasource/metadata Should handle manualChangelogUrls 1`] = ` { - "changelogUrl": "https://github.com/django/django/tree/master/docs/releases", + "changelogUrl": "https://github.com/flyingcircusio/pycountry/blob/master/HISTORY.txt", "releases": [ { "releaseTimestamp": "2018-07-13T10:14:17.000Z", @@ -53,7 +53,7 @@ exports[`modules/datasource/metadata Should handle manualChangelogUrls 1`] = ` "version": "2.2.0", }, ], - "sourceUrl": "https://github.com/django/django", + "sourceUrl": "https://github.com/flyingcircusio/pycountry", } `; diff --git a/lib/modules/datasource/api.ts b/lib/modules/datasource/api.ts index 4ba206675060c8..ff31d16c158362 100644 --- a/lib/modules/datasource/api.ts +++ b/lib/modules/datasource/api.ts @@ -51,6 +51,7 @@ import { PackagistDatasource } from './packagist'; import { PodDatasource } from './pod'; import { PuppetForgeDatasource } from './puppet-forge'; import { PypiDatasource } from './pypi'; +import { PythonVersionDatasource } from './python-version'; import { RepologyDatasource } from './repology'; import { RubyVersionDatasource } from './ruby-version'; import { RubyGemsDatasource } from './rubygems'; @@ -59,6 +60,7 @@ import { SbtPluginDatasource } from './sbt-plugin'; import { TerraformModuleDatasource } from './terraform-module'; import { TerraformProviderDatasource } from './terraform-provider'; import type { DatasourceApi } from './types'; +import { Unity3dDatasource } from './unity3d'; const api = new Map(); export default api; @@ -119,6 +121,7 @@ api.set(PackagistDatasource.id, new PackagistDatasource()); api.set(PodDatasource.id, new PodDatasource()); api.set(PuppetForgeDatasource.id, new PuppetForgeDatasource()); api.set(PypiDatasource.id, new PypiDatasource()); +api.set(PythonVersionDatasource.id, new PythonVersionDatasource()); api.set(RepologyDatasource.id, new RepologyDatasource()); api.set(RubyVersionDatasource.id, new RubyVersionDatasource()); api.set(RubyGemsDatasource.id, new RubyGemsDatasource()); @@ -126,3 +129,4 @@ api.set(SbtPackageDatasource.id, new SbtPackageDatasource()); api.set(SbtPluginDatasource.id, new SbtPluginDatasource()); api.set(TerraformModuleDatasource.id, new TerraformModuleDatasource()); api.set(TerraformProviderDatasource.id, new TerraformProviderDatasource()); +api.set(Unity3dDatasource.id, new Unity3dDatasource()); diff --git a/lib/modules/datasource/artifactory/index.ts b/lib/modules/datasource/artifactory/index.ts index 86fe6918b04b44..e66ffb4ebaac59 100644 --- a/lib/modules/datasource/artifactory/index.ts +++ b/lib/modules/datasource/artifactory/index.ts @@ -21,6 +21,10 @@ export class ArtifactoryDatasource extends Datasource { override readonly registryStrategy = 'merge'; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the date-like text, next to the version hyperlink tag in the results.'; + @cache({ namespace: `datasource-${datasource}`, key: ({ registryUrl, packageName }: GetReleasesConfig) => diff --git a/lib/modules/datasource/aws-machine-image/__snapshots__/index.spec.ts.snap b/lib/modules/datasource/aws-machine-image/__snapshots__/index.spec.ts.snap new file mode 100644 index 00000000000000..1f010f02364d44 --- /dev/null +++ b/lib/modules/datasource/aws-machine-image/__snapshots__/index.spec.ts.snap @@ -0,0 +1,115 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`modules/datasource/aws-machine-image/index getSortedAwsMachineImages() with 1 returned image 1`] = ` +[ + DescribeImagesCommand { + "deserialize": [Function], + "input": { + "Filters": [ + { + "Name": "owner-id", + "Values": [ + "602401143452", + ], + }, + { + "Name": "name", + "Values": [ + "1image", + ], + }, + ], + }, + "middlewareStack": { + "add": [Function], + "addRelativeTo": [Function], + "applyToStack": [Function], + "clone": [Function], + "concat": [Function], + "identify": [Function], + "identifyOnResolve": [Function], + "remove": [Function], + "removeByTag": [Function], + "resolve": [Function], + "use": [Function], + }, + "serialize": [Function], + }, +] +`; + +exports[`modules/datasource/aws-machine-image/index getSortedAwsMachineImages() with 3 returned images 1`] = ` +[ + DescribeImagesCommand { + "deserialize": [Function], + "input": { + "Filters": [ + { + "Name": "owner-id", + "Values": [ + "602401143452", + ], + }, + { + "Name": "name", + "Values": [ + "3images", + ], + }, + ], + }, + "middlewareStack": { + "add": [Function], + "addRelativeTo": [Function], + "applyToStack": [Function], + "clone": [Function], + "concat": [Function], + "identify": [Function], + "identifyOnResolve": [Function], + "remove": [Function], + "removeByTag": [Function], + "resolve": [Function], + "use": [Function], + }, + "serialize": [Function], + }, +] +`; + +exports[`modules/datasource/aws-machine-image/index getSortedAwsMachineImages() without returned images 1`] = ` +[ + DescribeImagesCommand { + "deserialize": [Function], + "input": { + "Filters": [ + { + "Name": "owner-id", + "Values": [ + "602401143452", + ], + }, + { + "Name": "name", + "Values": [ + "noiamge", + ], + }, + ], + }, + "middlewareStack": { + "add": [Function], + "addRelativeTo": [Function], + "applyToStack": [Function], + "clone": [Function], + "concat": [Function], + "identify": [Function], + "identifyOnResolve": [Function], + "remove": [Function], + "removeByTag": [Function], + "resolve": [Function], + "use": [Function], + }, + "serialize": [Function], + }, +] +`; diff --git a/lib/modules/datasource/aws-machine-image/index.spec.ts b/lib/modules/datasource/aws-machine-image/index.spec.ts index c0b3ad7b8569de..c966a7b8570923 100644 --- a/lib/modules/datasource/aws-machine-image/index.spec.ts +++ b/lib/modules/datasource/aws-machine-image/index.spec.ts @@ -146,40 +146,7 @@ describe('modules/datasource/aws-machine-image/index', () => { ); expect(res).toStrictEqual([image1, image2, image3]); expect(ec2Mock.calls()).toHaveLength(1); - expect(ec2Mock.calls()[0].args).toMatchInlineSnapshot(` - [ - DescribeImagesCommand { - "input": { - "Filters": [ - { - "Name": "owner-id", - "Values": [ - "602401143452", - ], - }, - { - "Name": "name", - "Values": [ - "3images", - ], - }, - ], - }, - "middlewareStack": { - "add": [Function], - "addRelativeTo": [Function], - "applyToStack": [Function], - "clone": [Function], - "concat": [Function], - "identify": [Function], - "remove": [Function], - "removeByTag": [Function], - "resolve": [Function], - "use": [Function], - }, - }, - ] - `); + expect(ec2Mock.calls()[0].args).toMatchSnapshot(); }); it('with 1 returned image', async () => { @@ -190,40 +157,7 @@ describe('modules/datasource/aws-machine-image/index', () => { ); expect(res).toStrictEqual([image3]); expect(ec2Mock.calls()).toHaveLength(1); - expect(ec2Mock.calls()[0].args).toMatchInlineSnapshot(` - [ - DescribeImagesCommand { - "input": { - "Filters": [ - { - "Name": "owner-id", - "Values": [ - "602401143452", - ], - }, - { - "Name": "name", - "Values": [ - "1image", - ], - }, - ], - }, - "middlewareStack": { - "add": [Function], - "addRelativeTo": [Function], - "applyToStack": [Function], - "clone": [Function], - "concat": [Function], - "identify": [Function], - "remove": [Function], - "removeByTag": [Function], - "resolve": [Function], - "use": [Function], - }, - }, - ] - `); + expect(ec2Mock.calls()[0].args).toMatchSnapshot(); }); it('without returned images', async () => { @@ -234,40 +168,7 @@ describe('modules/datasource/aws-machine-image/index', () => { ); expect(res).toStrictEqual([]); expect(ec2Mock.calls()).toHaveLength(1); - expect(ec2Mock.calls()[0].args).toMatchInlineSnapshot(` - [ - DescribeImagesCommand { - "input": { - "Filters": [ - { - "Name": "owner-id", - "Values": [ - "602401143452", - ], - }, - { - "Name": "name", - "Values": [ - "noiamge", - ], - }, - ], - }, - "middlewareStack": { - "add": [Function], - "addRelativeTo": [Function], - "applyToStack": [Function], - "clone": [Function], - "concat": [Function], - "identify": [Function], - "remove": [Function], - "removeByTag": [Function], - "resolve": [Function], - "use": [Function], - }, - }, - ] - `); + expect(ec2Mock.calls()[0].args).toMatchSnapshot(); }); }); diff --git a/lib/modules/datasource/aws-machine-image/index.ts b/lib/modules/datasource/aws-machine-image/index.ts index 8c15677505b0aa..455e1d33bad46e 100644 --- a/lib/modules/datasource/aws-machine-image/index.ts +++ b/lib/modules/datasource/aws-machine-image/index.ts @@ -18,6 +18,10 @@ export class AwsMachineImageDataSource extends Datasource { override readonly caching = true; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `CreationDate` field in the results.'; + override readonly defaultConfig = { // Because AMIs don't follow any versioning scheme, we override commitMessageExtra to remove the 'v' commitMessageExtra: 'to {{{newVersion}}}', diff --git a/lib/modules/datasource/azure-bicep-resource/index.spec.ts b/lib/modules/datasource/azure-bicep-resource/index.spec.ts index 4afbff7d53e9ee..0b1dff2be12647 100644 --- a/lib/modules/datasource/azure-bicep-resource/index.spec.ts +++ b/lib/modules/datasource/azure-bicep-resource/index.spec.ts @@ -28,7 +28,7 @@ describe('modules/datasource/azure-bicep-resource/index', () => { expect(result).toBeNull(); }); - it('should return versions when package is a function', async () => { + it('should return null when package is a function', async () => { httpMock .scope(gitHubHost) .get(indexPath) @@ -57,23 +57,10 @@ describe('modules/datasource/azure-bicep-resource/index', () => { const azureBicepResourceDatasource = new AzureBicepResourceDatasource(); const result = await azureBicepResourceDatasource.getReleases({ - packageName: 'Microsoft.Billing/billingAccounts', + packageName: 'unknown', }); - expect(result).toEqual({ - releases: [ - { - version: '2019-10-01-preview', - changelogUrl: - 'https://learn.microsoft.com/en-us/azure/templates/microsoft.billing/change-log/billingaccounts#2019-10-01-preview', - }, - { - version: '2020-05-01', - changelogUrl: - 'https://learn.microsoft.com/en-us/azure/templates/microsoft.billing/change-log/billingaccounts#2020-05-01', - }, - ], - }); + expect(result).toBeNull(); }); it('should return versions when package is a resource', async () => { @@ -117,4 +104,46 @@ describe('modules/datasource/azure-bicep-resource/index', () => { ], }); }); + + it('should return versions when package is a resource and a function', async () => { + httpMock + .scope(gitHubHost) + .get(indexPath) + .reply( + 200, + codeBlock` + { + "resources": { + "Microsoft.OperationalInsights/workspaces@2023-09-01": { + "$ref": "operationalinsights/microsoft.operationalinsights/2023-09-01/types.json#/31" + } + }, + "resourceFunctions": { + "microsoft.operationalinsights/workspaces": { + "2015-03-20": [ + { + "$ref": "operationalinsights/workspaces/2015-03-20/types.json#/304" + } + ] + } + } + } + `, + ); + + const azureBicepResourceDatasource = new AzureBicepResourceDatasource(); + const result = await azureBicepResourceDatasource.getReleases({ + packageName: 'Microsoft.OperationalInsights/workspaces', + }); + + expect(result).toEqual({ + releases: [ + { + version: '2023-09-01', + changelogUrl: + 'https://learn.microsoft.com/en-us/azure/templates/microsoft.operationalinsights/change-log/workspaces#2023-09-01', + }, + ], + }); + }); }); diff --git a/lib/modules/datasource/azure-bicep-resource/schema.ts b/lib/modules/datasource/azure-bicep-resource/schema.ts index 70dd2938f54f03..c94db6faea33de 100644 --- a/lib/modules/datasource/azure-bicep-resource/schema.ts +++ b/lib/modules/datasource/azure-bicep-resource/schema.ts @@ -3,9 +3,8 @@ import { z } from 'zod'; export const BicepResourceVersionIndex = z .object({ resources: z.record(z.string(), z.unknown()), - resourceFunctions: z.record(z.string(), z.record(z.string(), z.unknown())), }) - .transform(({ resources, resourceFunctions }) => { + .transform(({ resources }) => { const releaseMap = new Map(); for (const resourceReference of Object.keys(resources)) { @@ -15,11 +14,6 @@ export const BicepResourceVersionIndex = z releaseMap.set(type, versions); } - for (const [type, versionMap] of Object.entries(resourceFunctions)) { - const versions = Object.keys(versionMap); - releaseMap.set(type, versions); - } - return Object.fromEntries(releaseMap); }); diff --git a/lib/modules/datasource/bitbucket-tags/index.spec.ts b/lib/modules/datasource/bitbucket-tags/index.spec.ts index 4a8780d689e67f..08f8e8d9a682f0 100644 --- a/lib/modules/datasource/bitbucket-tags/index.spec.ts +++ b/lib/modules/datasource/bitbucket-tags/index.spec.ts @@ -62,7 +62,11 @@ describe('modules/datasource/bitbucket-tags/index', () => { httpMock .scope('https://api.bitbucket.org') .get('/2.0/repositories/some/dep2') - .reply(200, { mainbranch: { name: 'master' } }); + .reply(200, { + mainbranch: { name: 'master' }, + uuid: '123', + full_name: 'some/repo', + }); httpMock .scope('https://api.bitbucket.org') .get('/2.0/repositories/some/dep2/commits/master') @@ -87,7 +91,11 @@ describe('modules/datasource/bitbucket-tags/index', () => { httpMock .scope('https://api.bitbucket.org') .get('/2.0/repositories/some/dep2') - .reply(200, { mainbranch: { name: 'master' } }); + .reply(200, { + mainbranch: { name: 'master' }, + uuid: '123', + full_name: 'some/repo', + }); httpMock .scope('https://api.bitbucket.org') .get('/2.0/repositories/some/dep2/commits/master') diff --git a/lib/modules/datasource/bitbucket-tags/index.ts b/lib/modules/datasource/bitbucket-tags/index.ts index 7a2457c0c3a324..165336cce165b9 100644 --- a/lib/modules/datasource/bitbucket-tags/index.ts +++ b/lib/modules/datasource/bitbucket-tags/index.ts @@ -2,7 +2,8 @@ import { cache } from '../../../util/cache/package/decorator'; import type { PackageCacheNamespace } from '../../../util/cache/package/types'; import { BitbucketHttp } from '../../../util/http/bitbucket'; import { ensureTrailingSlash } from '../../../util/url'; -import type { PagedResult, RepoInfoBody } from '../../platform/bitbucket/types'; +import { RepoInfo } from '../../platform/bitbucket/schema'; +import type { PagedResult } from '../../platform/bitbucket/types'; import { Datasource } from '../datasource'; import type { DigestConfig, GetReleasesConfig, ReleaseResult } from '../types'; import type { BitbucketCommit, BitbucketTag } from './types'; @@ -18,6 +19,13 @@ export class BitbucketTagsDatasource extends Datasource { static readonly defaultRegistryUrls = ['https://bitbucket.org']; + static readonly releaseTimestampSupport = true; + static readonly releaseTimestampNote = + 'The release timestamp is determined from the `date` field in the results.'; + static readonly sourceUrlSupport = 'package'; + static readonly sourceUrlNote = + 'The source URL is determined by using the `packageName` and `registryUrl`.'; + static readonly cacheNamespace: PackageCacheNamespace = `datasource-${BitbucketTagsDatasource.id}`; constructor() { @@ -102,10 +110,8 @@ export class BitbucketTagsDatasource extends Datasource { }) async getMainBranch(repo: string): Promise { return ( - await this.bitbucketHttp.getJson( - `/2.0/repositories/${repo}`, - ) - ).body.mainbranch.name; + await this.bitbucketHttp.getJson(`/2.0/repositories/${repo}`, RepoInfo) + ).body.mainbranch; } // getDigest fetched the latest commit for repository main branch diff --git a/lib/modules/datasource/cdnjs/index.ts b/lib/modules/datasource/cdnjs/index.ts index 6a142235e97fe3..daf17b6961b725 100644 --- a/lib/modules/datasource/cdnjs/index.ts +++ b/lib/modules/datasource/cdnjs/index.ts @@ -28,6 +28,10 @@ export class CdnJsDatasource extends Datasource { override readonly defaultRegistryUrls = ['https://api.cdnjs.com/']; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined from the `repository` field in the results.'; + @cache({ namespace: `datasource-${CdnJsDatasource.id}`, key: ({ packageName }: GetReleasesConfig) => packageName.split('/')[0], diff --git a/lib/modules/datasource/clojure/__snapshots__/index.spec.ts.snap b/lib/modules/datasource/clojure/__snapshots__/index.spec.ts.snap index 54e15f3e176c63..483eac0146289c 100644 --- a/lib/modules/datasource/clojure/__snapshots__/index.spec.ts.snap +++ b/lib/modules/datasource/clojure/__snapshots__/index.spec.ts.snap @@ -6,6 +6,7 @@ exports[`modules/datasource/clojure/index falls back to next registry url 1`] = "group": "org.example", "homepage": "https://package.example.org/about", "name": "package", + "packageScope": "org.example", "registryUrl": "https://clojars.org/repo", "releases": [ { @@ -53,6 +54,7 @@ exports[`modules/datasource/clojure/index returns releases from custom repositor "group": "org.example", "homepage": "https://package.example.org/about", "name": "package", + "packageScope": "org.example", "registryUrl": "https://custom.registry.renovatebot.com", "releases": [ { @@ -80,6 +82,7 @@ exports[`modules/datasource/clojure/index skips registry with invalid XML 1`] = "group": "org.example", "homepage": "https://package.example.org/about", "name": "package", + "packageScope": "org.example", "registryUrl": "https://clojars.org/repo", "releases": [ { @@ -107,6 +110,7 @@ exports[`modules/datasource/clojure/index skips registry with invalid metadata s "group": "org.example", "homepage": "https://package.example.org/about", "name": "package", + "packageScope": "org.example", "registryUrl": "https://clojars.org/repo", "releases": [ { diff --git a/lib/modules/datasource/common.spec.ts b/lib/modules/datasource/common.spec.ts index 703283f9c0e4a7..a86c6a2d3f43de 100644 --- a/lib/modules/datasource/common.spec.ts +++ b/lib/modules/datasource/common.spec.ts @@ -226,6 +226,25 @@ describe('modules/datasource/common', () => { releases: [{ version: '1.0.0' }, { version: '2.0.0' }], }); }); + + it('should match exact constraints', () => { + const config = { + datasource: 'pypi', + packageName: 'bar', + versioning: 'pep440', + constraintsFiltering: 'strict' as const, + constraints: { python: '>=3.8' }, + }; + const releaseResult = { + releases: [ + { version: '1.0.0', constraints: { python: ['^1.0.0'] } }, + { version: '2.0.0', constraints: { python: ['>=3.8'] } }, + ], + }; + expect(applyConstraintsFiltering(releaseResult, config)).toEqual({ + releases: [{ version: '2.0.0' }], + }); + }); }); describe('applyVersionCompatibility', () => { diff --git a/lib/modules/datasource/common.ts b/lib/modules/datasource/common.ts index 6b952da4f49ac7..efda4b39c46799 100644 --- a/lib/modules/datasource/common.ts +++ b/lib/modules/datasource/common.ts @@ -208,6 +208,11 @@ export function applyConstraintsFiltering< break; } + if (configConstraint === releaseConstraint) { + satisfiesConstraints = true; + break; + } + if (versioning.subset?.(configConstraint, releaseConstraint)) { satisfiesConstraints = true; break; diff --git a/lib/modules/datasource/conan/index.ts b/lib/modules/datasource/conan/index.ts index c4a7179b781ae6..2a82502dc00790 100644 --- a/lib/modules/datasource/conan/index.ts +++ b/lib/modules/datasource/conan/index.ts @@ -38,6 +38,10 @@ export class ConanDatasource extends Datasource { githubHttp: GithubHttp; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is supported only if the package is served from the Artifactory servers. In which case we determine it from the `properties[conan.package.url]` field in the results.'; + constructor(id = ConanDatasource.id) { super(id); this.githubHttp = new GithubHttp(id); diff --git a/lib/modules/datasource/conda/index.ts b/lib/modules/datasource/conda/index.ts index b32cd4de63748b..980eb8de9be103 100644 --- a/lib/modules/datasource/conda/index.ts +++ b/lib/modules/datasource/conda/index.ts @@ -23,6 +23,10 @@ export class CondaDatasource extends Datasource { override readonly caching = true; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined from the `dev_url` field in the results.'; + @cache({ namespace: `datasource-${datasource}`, key: ({ registryUrl, packageName }: GetReleasesConfig) => diff --git a/lib/modules/datasource/cpan/index.ts b/lib/modules/datasource/cpan/index.ts index ee8a808da54bf9..85436a1d925463 100644 --- a/lib/modules/datasource/cpan/index.ts +++ b/lib/modules/datasource/cpan/index.ts @@ -18,6 +18,10 @@ export class CpanDatasource extends Datasource { override readonly defaultVersioning = perlVersioning.id; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `date` field in the results.'; + @cache({ namespace: `datasource-${CpanDatasource.id}`, key: ({ packageName }: GetReleasesConfig) => `${packageName}`, diff --git a/lib/modules/datasource/crate/__fixtures__/amethyst b/lib/modules/datasource/crate/__fixtures__/amethyst index 486375eaedf9a3..e65826c2c196e7 100644 --- a/lib/modules/datasource/crate/__fixtures__/amethyst +++ b/lib/modules/datasource/crate/__fixtures__/amethyst @@ -16,4 +16,4 @@ {"name":"amethyst","vers":"0.8.0","deps":[{"name":"amethyst_animation","req":"^0.3.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_assets","req":"^0.4.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_audio","req":"^0.3.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_config","req":"^0.7.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_controls","req":"^0.2.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_core","req":"^0.3.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_input","req":"^0.4.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_locale","req":"^0.2.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_renderer","req":"^0.8.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_ui","req":"^0.3.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_utils","req":"^0.3.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"derivative","req":"^1.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"fern","req":"^0.5","features":["colored"],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"log","req":"^0.4","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"rayon","req":"^1.0.1","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"rustc_version_runtime","req":"^0.1","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"thread_profiler","req":"^0.1","features":[],"optional":true,"default_features":true,"target":null,"kind":"normal"},{"name":"winit","req":"^0.15","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_gltf","req":"^0.3.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev"},{"name":"env_logger","req":"^0.5.10","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev"},{"name":"genmesh","req":"^0.6","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev"},{"name":"ron","req":"^0.2","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev"},{"name":"serde","req":"^1.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev"},{"name":"serde_derive","req":"^1.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev"},{"name":"vergen","req":"^0.1","features":[],"optional":false,"default_features":true,"target":null,"kind":"build"}],"cksum":"f92e4a150ee7d2c22d4dfc1b921b22316e2b5a2823e255ed0c573bcd0f3e5e76","features":{"sdl_controller":["amethyst_input/sdl_controller"],"profiler":["thread_profiler","thread_profiler/thread_profiler","amethyst_animation/profiler","amethyst_assets/profiler","amethyst_audio/profiler","amethyst_config/profiler","amethyst_core/profiler","amethyst_controls/profiler","amethyst_input/profiler","amethyst_locale/profiler","amethyst_renderer/profiler","amethyst_ui/profiler","amethyst_utils/profiler"],"nightly":["amethyst_animation/nightly","amethyst_assets/nightly","amethyst_audio/nightly","amethyst_config/nightly","amethyst_core/nightly","amethyst_controls/nightly","amethyst_renderer/nightly","amethyst_input/nightly","amethyst_ui/nightly","amethyst_utils/nightly"]},"yanked":false,"links":null} {"name":"amethyst","vers":"0.9.0","deps":[{"name":"amethyst_animation","req":"^0.4.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_assets","req":"^0.5.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_audio","req":"^0.4.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_config","req":"^0.8.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_controls","req":"^0.3.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_core","req":"^0.4.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_derive","req":"^0.2.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_input","req":"^0.5.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_locale","req":"^0.3.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_network","req":"^0.2.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_renderer","req":"^0.9.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_ui","req":"^0.4.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_utils","req":"^0.4.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"derivative","req":"^1.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"fern","req":"^0.5","features":["colored"],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"log","req":"^0.4","features":["serde"],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"rayon","req":"^1.0.2","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"rustc_version_runtime","req":"^0.1","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"serde","req":"^1.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"serde_derive","req":"^1.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"thread_profiler","req":"^0.3","features":[],"optional":true,"default_features":true,"target":null,"kind":"normal"},{"name":"winit","req":"^0.17","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_gltf","req":"^0.4.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev"},{"name":"env_logger","req":"^0.5.13","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev"},{"name":"genmesh","req":"^0.6","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev"},{"name":"ron","req":"^0.4","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev"},{"name":"vergen","req":"^2.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"build"}],"cksum":"c596939802d52ecceff96a0ea507d4b8f1a268b15797e445df94f5307027a923","features":{"sdl_controller":["amethyst_input/sdl_controller"],"profiler":["thread_profiler","thread_profiler/thread_profiler","amethyst_animation/profiler","amethyst_assets/profiler","amethyst_audio/profiler","amethyst_config/profiler","amethyst_core/profiler","amethyst_controls/profiler","amethyst_input/profiler","amethyst_locale/profiler","amethyst_renderer/profiler","amethyst_ui/profiler","amethyst_utils/profiler"],"saveload":["amethyst_core/saveload"],"json":["amethyst_assets/json"],"nightly":["amethyst_animation/nightly","amethyst_assets/nightly","amethyst_audio/nightly","amethyst_config/nightly","amethyst_core/nightly","amethyst_controls/nightly","amethyst_network/nightly","amethyst_renderer/nightly","amethyst_input/nightly","amethyst_ui/nightly","amethyst_utils/nightly"]},"yanked":false,"links":null} {"name":"amethyst","vers":"0.10.0","deps":[{"name":"amethyst_animation","req":"^0.5.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_assets","req":"^0.6.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_audio","req":"^0.5.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_config","req":"^0.9.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_controls","req":"^0.4.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_core","req":"^0.5.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_derive","req":"^0.3.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_input","req":"^0.6.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_locale","req":"^0.4.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_network","req":"^0.3.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_renderer","req":"^0.10.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_ui","req":"^0.5.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_utils","req":"^0.5.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"crossbeam-channel","req":"^0.3.1","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"derivative","req":"^1.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"fern","req":"^0.5","features":["colored"],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"log","req":"^0.4","features":["serde"],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"rayon","req":"^1.0.2","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"rustc_version_runtime","req":"^0.1","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"serde","req":"^1.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"serde_derive","req":"^1.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"thread_profiler","req":"^0.3","features":[],"optional":true,"default_features":true,"target":null,"kind":"normal"},{"name":"winit","req":"^0.18","features":["serde"],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_gltf","req":"^0.5.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev"},{"name":"env_logger","req":"^0.5.13","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev"},{"name":"genmesh","req":"^0.6","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev"},{"name":"ron","req":"^0.4","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev"},{"name":"vergen","req":"^2.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"build"}],"cksum":"2aeb884ea509846b98408d1c5c5524a894533bd147e66d29b7efc95c4047b73b","features":{"sdl_controller":["amethyst_input/sdl_controller"],"nightly":["amethyst_animation/nightly","amethyst_assets/nightly","amethyst_audio/nightly","amethyst_config/nightly","amethyst_core/nightly","amethyst_controls/nightly","amethyst_network/nightly","amethyst_renderer/nightly","amethyst_input/nightly","amethyst_ui/nightly","amethyst_utils/nightly"],"profiler":["thread_profiler","thread_profiler/thread_profiler","amethyst_animation/profiler","amethyst_assets/profiler","amethyst_audio/profiler","amethyst_config/profiler","amethyst_core/profiler","amethyst_controls/profiler","amethyst_input/profiler","amethyst_locale/profiler","amethyst_renderer/profiler","amethyst_ui/profiler","amethyst_utils/profiler"],"saveload":["amethyst_core/saveload"],"json":["amethyst_assets/json"]},"yanked":false,"links":null} -{"name":"amethyst","vers":"0.10.1","deps":[{"name":"amethyst_animation","req":"^0.5.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_assets","req":"^0.6.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_audio","req":"^0.5.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_config","req":"^0.9.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_controls","req":"^0.4.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_core","req":"^0.5.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_derive","req":"^0.3.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_input","req":"^0.6.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_locale","req":"^0.4.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_network","req":"^0.3.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_renderer","req":"^0.10.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_ui","req":"^0.5.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_utils","req":"^0.5.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"crossbeam-channel","req":"^0.3.1","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"derivative","req":"^1.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"fern","req":"^0.5","features":["colored"],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"log","req":"^0.4.6","features":["serde"],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"rayon","req":"^1.0.2","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"rustc_version_runtime","req":"^0.1","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"serde","req":"^1.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"serde_derive","req":"^1.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"thread_profiler","req":"^0.3","features":[],"optional":true,"default_features":true,"target":null,"kind":"normal"},{"name":"winit","req":"^0.18","features":["serde"],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_gltf","req":"^0.5.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev"},{"name":"env_logger","req":"^0.5.13","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev"},{"name":"genmesh","req":"^0.6","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev"},{"name":"ron","req":"^0.4","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev"},{"name":"vergen","req":"^3.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"build"}],"cksum":"ab13cb760f6ff4b6a23f82599a8c64a77a45ca21cc66b27e1a72a863152747be","features":{"json":["amethyst_assets/json"],"saveload":["amethyst_core/saveload"],"sdl_controller":["amethyst_input/sdl_controller"],"nightly":["amethyst_animation/nightly","amethyst_assets/nightly","amethyst_audio/nightly","amethyst_config/nightly","amethyst_core/nightly","amethyst_controls/nightly","amethyst_network/nightly","amethyst_renderer/nightly","amethyst_input/nightly","amethyst_ui/nightly","amethyst_utils/nightly"],"profiler":["thread_profiler","thread_profiler/thread_profiler","amethyst_animation/profiler","amethyst_assets/profiler","amethyst_audio/profiler","amethyst_config/profiler","amethyst_core/profiler","amethyst_controls/profiler","amethyst_input/profiler","amethyst_locale/profiler","amethyst_renderer/profiler","amethyst_ui/profiler","amethyst_utils/profiler"]},"yanked":true,"links":null} +{"name":"amethyst","vers":"0.10.1","deps":[{"name":"amethyst_animation","req":"^0.5.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_assets","req":"^0.6.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_audio","req":"^0.5.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_config","req":"^0.9.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_controls","req":"^0.4.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_core","req":"^0.5.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_derive","req":"^0.3.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_input","req":"^0.6.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_locale","req":"^0.4.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_network","req":"^0.3.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_renderer","req":"^0.10.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_ui","req":"^0.5.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_utils","req":"^0.5.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"crossbeam-channel","req":"^0.3.1","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"derivative","req":"^1.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"fern","req":"^0.5","features":["colored"],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"log","req":"^0.4.6","features":["serde"],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"rayon","req":"^1.0.2","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"rustc_version_runtime","req":"^0.1","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"serde","req":"^1.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"serde_derive","req":"^1.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"thread_profiler","req":"^0.3","features":[],"optional":true,"default_features":true,"target":null,"kind":"normal"},{"name":"winit","req":"^0.18","features":["serde"],"optional":false,"default_features":true,"target":null,"kind":"normal"},{"name":"amethyst_gltf","req":"^0.5.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev"},{"name":"env_logger","req":"^0.5.13","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev"},{"name":"genmesh","req":"^0.6","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev"},{"name":"ron","req":"^0.4","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev"},{"name":"vergen","req":"^3.0","features":[],"optional":false,"default_features":true,"target":null,"kind":"build"}],"cksum":"ab13cb760f6ff4b6a23f82599a8c64a77a45ca21cc66b27e1a72a863152747be","features":{"json":["amethyst_assets/json"],"saveload":["amethyst_core/saveload"],"sdl_controller":["amethyst_input/sdl_controller"],"nightly":["amethyst_animation/nightly","amethyst_assets/nightly","amethyst_audio/nightly","amethyst_config/nightly","amethyst_core/nightly","amethyst_controls/nightly","amethyst_network/nightly","amethyst_renderer/nightly","amethyst_input/nightly","amethyst_ui/nightly","amethyst_utils/nightly"],"profiler":["thread_profiler","thread_profiler/thread_profiler","amethyst_animation/profiler","amethyst_assets/profiler","amethyst_audio/profiler","amethyst_config/profiler","amethyst_core/profiler","amethyst_controls/profiler","amethyst_input/profiler","amethyst_locale/profiler","amethyst_renderer/profiler","amethyst_ui/profiler","amethyst_utils/profiler"]},"yanked":true,"links":null,"rust_version":"1.60.0"} diff --git a/lib/modules/datasource/crate/__fixtures__/libc b/lib/modules/datasource/crate/__fixtures__/libc index 346b382c83a3e5..9ba974fda052cd 100644 --- a/lib/modules/datasource/crate/__fixtures__/libc +++ b/lib/modules/datasource/crate/__fixtures__/libc @@ -62,4 +62,4 @@ {"name":"libc","vers":"0.2.48","deps":[{"name":"rustc-std-workspace-core","req":"^1.0.0","features":[],"optional":true,"default_features":true,"target":null,"kind":"normal"}],"cksum":"e962c7641008ac010fa60a7dfdc1712449f29c44ef2d4702394aea943ee75047","features":{"use_std":[],"default":["use_std"],"rustc-dep-of-std":["align","rustc-std-workspace-core"],"align":[]},"yanked":false,"links":null} {"name":"libc","vers":"0.2.49","deps":[{"name":"rustc-std-workspace-core","req":"^1.0.0","features":[],"optional":true,"default_features":true,"target":null,"kind":"normal"}],"cksum":"413f3dfc802c5dc91dc570b05125b6cda9855edfaa9825c9849807876376e70e","features":{"use_std":[],"align":[],"extra_traits":[],"default":["use_std"],"rustc-dep-of-std":["align","rustc-std-workspace-core"]},"yanked":false,"links":null} {"name":"libc","vers":"0.2.50","deps":[{"name":"rustc-std-workspace-core","req":"^1.0.0","features":[],"optional":true,"default_features":true,"target":null,"kind":"normal"}],"cksum":"aab692d7759f5cd8c859e169db98ae5b52c924add2af5fbbca11d12fefb567c1","features":{"extra_traits":[],"use_std":[],"rustc-dep-of-std":["align","rustc-std-workspace-core"],"align":[],"default":["use_std"]},"yanked":false,"links":null} -{"name":"libc","vers":"0.2.51","deps":[{"name":"rustc-std-workspace-core","req":"^1.0.0","features":[],"optional":true,"default_features":true,"target":null,"kind":"normal"}],"cksum":"bedcc7a809076656486ffe045abeeac163da1b558e963a31e29fbfbeba916917","features":{"align":[],"rustc-dep-of-std":["align","rustc-std-workspace-core"],"extra_traits":[],"use_std":[],"default":["use_std"]},"yanked":false,"links":null} +{"name":"libc","vers":"0.2.51+metadata","deps":[{"name":"rustc-std-workspace-core","req":"^1.0.0","features":[],"optional":true,"default_features":true,"target":null,"kind":"normal"}],"cksum":"bedcc7a809076656486ffe045abeeac163da1b558e963a31e29fbfbeba916917","features":{"align":[],"rustc-dep-of-std":["align","rustc-std-workspace-core"],"extra_traits":[],"use_std":[],"default":["use_std"]},"yanked":false,"links":null} diff --git a/lib/modules/datasource/crate/index.ts b/lib/modules/datasource/crate/index.ts index 5e6d07b2529c8c..24ec9ce47e02c8 100644 --- a/lib/modules/datasource/crate/index.ts +++ b/lib/modules/datasource/crate/index.ts @@ -35,6 +35,10 @@ export class CrateDatasource extends Datasource { static readonly CRATES_IO_API_BASE_URL = 'https://crates.io/api/v1/'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined from the `repository` field in the results.'; + @cache({ namespace: `datasource-${CrateDatasource.id}`, key: ({ registryUrl, packageName }: GetReleasesConfig) => @@ -97,11 +101,16 @@ export class CrateDatasource extends Datasource { result.releases = lines .map((version) => { const release: Release = { - version: version.vers, + version: version.vers.replace(/\+.*$/, ''), }; if (version.yanked) { release.isDeprecated = true; } + if (version.rust_version) { + release.constraints = { + rust: [version.rust_version], + }; + } return release; }) .filter((release) => release.version); diff --git a/lib/modules/datasource/crate/types.ts b/lib/modules/datasource/crate/types.ts index a7b59efbcb1145..0011fadd204449 100644 --- a/lib/modules/datasource/crate/types.ts +++ b/lib/modules/datasource/crate/types.ts @@ -27,6 +27,7 @@ export interface RegistryInfo { export interface CrateRecord { vers: string; yanked: boolean; + rust_version?: string; } export interface CrateMetadata { diff --git a/lib/modules/datasource/custom/index.spec.ts b/lib/modules/datasource/custom/index.spec.ts index 001e807c47fe4d..10976ae507f7fb 100644 --- a/lib/modules/datasource/custom/index.spec.ts +++ b/lib/modules/datasource/custom/index.spec.ts @@ -697,4 +697,13 @@ describe('modules/datasource/custom/index', () => { expect(result).toEqual(expected); }); }); + + describe('getDigest', () => { + it('returns null as digest should be provided in releases', async () => { + const digest = await new CustomDatasource().getDigest({ + packageName: 'my-package', + }); + expect(digest).toBeNull(); + }); + }); }); diff --git a/lib/modules/datasource/custom/index.ts b/lib/modules/datasource/custom/index.ts index 64d0fcfff9d757..f9ca969710b2ba 100644 --- a/lib/modules/datasource/custom/index.ts +++ b/lib/modules/datasource/custom/index.ts @@ -2,7 +2,7 @@ import is from '@sindresorhus/is'; import jsonata from 'jsonata'; import { logger } from '../../../logger'; import { Datasource } from '../datasource'; -import type { GetReleasesConfig, ReleaseResult } from '../types'; +import type { DigestConfig, GetReleasesConfig, ReleaseResult } from '../types'; import { fetchers } from './formats'; import { ReleaseResultZodSchema } from './schema'; import { getCustomConfig } from './utils'; @@ -57,4 +57,12 @@ export class CustomDatasource extends Datasource { return null; } } + + override getDigest( + { packageName }: DigestConfig, + newValue?: string, + ): Promise { + // Return null here to support setting a digest: value can be provided digest in getReleases + return Promise.resolve(null); + } } diff --git a/lib/modules/datasource/custom/readme.md b/lib/modules/datasource/custom/readme.md index 1a6facfa034016..7391076f901b65 100644 --- a/lib/modules/datasource/custom/readme.md +++ b/lib/modules/datasource/custom/readme.md @@ -7,11 +7,11 @@ This example shows how to update the `k3s.version` file with a custom datasource Options: -| option | default | description | -| -------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| defaultRegistryUrlTemplate | "" | URL used if no `registryUrl` is provided when looking up new releases | -| format | "json" | format used by the API. Available values are: `json`, `plain`, `yaml`, `html` | -| transformTemplates | [] | [JSONata rules](https://docs.jsonata.org/simple) to transform the API output. Each rule will be evaluated after another and the result will be used as input to the next | +| option | default | description | +| -------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| defaultRegistryUrlTemplate | `""` | URL used if no `registryUrl` is provided when looking up new releases | +| format | `"json"` | format used by the API. Available values are: `json`, `plain`, `yaml`, `html` | +| transformTemplates | `[]` | [JSONata rules](https://docs.jsonata.org/simple) to transform the API output. Each rule will be evaluated after another and the result will be used as input to the next | Available template variables: diff --git a/lib/modules/datasource/dart-version/index.ts b/lib/modules/datasource/dart-version/index.ts index a3aa8c45687a51..23f95ccab89124 100644 --- a/lib/modules/datasource/dart-version/index.ts +++ b/lib/modules/datasource/dart-version/index.ts @@ -21,6 +21,10 @@ export class DartVersionDatasource extends Datasource { private readonly channels = ['stable', 'beta', 'dev']; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'We use the URL: https://github.com/dart-lang/sdk.'; + async getReleases({ registryUrl, }: GetReleasesConfig): Promise { diff --git a/lib/modules/datasource/dart/index.ts b/lib/modules/datasource/dart/index.ts index 30da6a53c23e5c..88e549cfeb8903 100644 --- a/lib/modules/datasource/dart/index.ts +++ b/lib/modules/datasource/dart/index.ts @@ -15,6 +15,13 @@ export class DartDatasource extends Datasource { override readonly defaultRegistryUrls = ['https://pub.dartlang.org/']; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `published` field in the results.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined from the `repository` field of the latest release object in the results.'; + async getReleases({ packageName, registryUrl, diff --git a/lib/modules/datasource/datasource.ts b/lib/modules/datasource/datasource.ts index 2d88417201d244..fce559644110b4 100644 --- a/lib/modules/datasource/datasource.ts +++ b/lib/modules/datasource/datasource.ts @@ -6,6 +6,7 @@ import type { GetReleasesConfig, RegistryStrategy, ReleaseResult, + SourceUrlSupport, } from './types'; export abstract class Datasource implements DatasourceApi { @@ -25,6 +26,12 @@ export abstract class Datasource implements DatasourceApi { registryStrategy: RegistryStrategy | undefined = 'first'; + releaseTimestampSupport = false; + releaseTimestampNote?: string | undefined; + + sourceUrlSupport: SourceUrlSupport = 'none'; + sourceUrlNote?: string | undefined; + protected http: Http; abstract getReleases( diff --git a/lib/modules/datasource/deno/index.ts b/lib/modules/datasource/deno/index.ts index 425cf975c065f7..e396c3b7d8a8b4 100644 --- a/lib/modules/datasource/deno/index.ts +++ b/lib/modules/datasource/deno/index.ts @@ -22,6 +22,13 @@ export class DenoDatasource extends Datasource { override readonly defaultRegistryUrls = ['https://apiland.deno.dev']; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `uploaded_at` field in the results.'; + override readonly sourceUrlSupport = 'release'; + override readonly sourceUrlNote = + 'The source URL is determined from the `repository` field in the results.'; + constructor() { super(DenoDatasource.id); } diff --git a/lib/modules/datasource/docker/common.ts b/lib/modules/datasource/docker/common.ts index 99945705ce7e7c..1ccc12c66a648d 100644 --- a/lib/modules/datasource/docker/common.ts +++ b/lib/modules/datasource/docker/common.ts @@ -32,11 +32,11 @@ import { googleRegex } from './google'; import type { OciHelmConfig } from './schema'; import type { RegistryRepository } from './types'; -export const dockerDatasourceId = 'docker' as const; +export const dockerDatasourceId = 'docker'; -export const imageUrlLabel = 'org.opencontainers.image.url' as const; +export const imageUrlLabel = 'org.opencontainers.image.url'; -export const sourceLabel = 'org.opencontainers.image.source' as const; +export const sourceLabel = 'org.opencontainers.image.source'; export const sourceLabels = [sourceLabel, 'org.label-schema.vcs-url'] as const; export const gitRefLabel = 'org.opencontainers.image.revision'; diff --git a/lib/modules/datasource/docker/dockerhub-cache.spec.ts b/lib/modules/datasource/docker/dockerhub-cache.spec.ts new file mode 100644 index 00000000000000..fe8ad65504229e --- /dev/null +++ b/lib/modules/datasource/docker/dockerhub-cache.spec.ts @@ -0,0 +1,176 @@ +import { mocked } from '../../../../test/util'; +import * as _packageCache from '../../../util/cache/package'; +import { DockerHubCache, DockerHubCacheData } from './dockerhub-cache'; +import type { DockerHubTag } from './schema'; + +jest.mock('../../../util/cache/package'); +const packageCache = mocked(_packageCache); + +function oldCacheData(): DockerHubCacheData { + return { + items: { + 1: { + id: 1, + last_updated: '2022-01-01', + name: '1', + tag_last_pushed: '2022-01-01', + digest: 'sha256:111', + }, + 2: { + id: 2, + last_updated: '2022-01-02', + name: '2', + tag_last_pushed: '2022-01-02', + digest: 'sha256:222', + }, + 3: { + id: 3, + last_updated: '2022-01-03', + name: '3', + tag_last_pushed: '2022-01-03', + digest: 'sha256:333', + }, + }, + updatedAt: '2022-01-01', + }; +} + +function newItem(): DockerHubTag { + return { + id: 4, + last_updated: '2022-01-04', + name: '4', + tag_last_pushed: '2022-01-04', + digest: 'sha256:444', + }; +} + +function newCacheData(): DockerHubCacheData { + const { items } = oldCacheData(); + const item = newItem(); + return { + items: { + ...items, + [item.id]: item, + }, + updatedAt: '2022-01-04', + }; +} + +describe('modules/datasource/docker/dockerhub-cache', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + const dockerRepository = 'foo/bar'; + + it('initializes empty cache', async () => { + packageCache.get.mockResolvedValue(undefined); + + const res = await DockerHubCache.init(dockerRepository); + + expect(res).toEqual({ + dockerRepository, + cache: { + items: {}, + updatedAt: null, + }, + isChanged: false, + }); + }); + + it('initializes cache with data', async () => { + const oldCache = oldCacheData(); + packageCache.get.mockResolvedValue(oldCache); + + const res = await DockerHubCache.init(dockerRepository); + + expect(res).toEqual({ + dockerRepository, + cache: oldCache, + isChanged: false, + }); + }); + + it('reconciles new items', async () => { + const oldCache = oldCacheData(); + const newCache = newCacheData(); + + packageCache.get.mockResolvedValue(oldCache); + const cache = await DockerHubCache.init(dockerRepository); + const newItems: DockerHubTag[] = [newItem()]; + + const needNextPage = cache.reconcile(newItems); + + expect(needNextPage).toBe(true); + expect(cache).toEqual({ + cache: newCache, + dockerRepository: 'foo/bar', + isChanged: true, + }); + + const res = cache.getItems(); + expect(res).toEqual(Object.values(newCache.items)); + + await cache.save(); + expect(packageCache.set).toHaveBeenCalledWith( + 'datasource-docker-hub-cache', + 'foo/bar', + newCache, + 3 * 60 * 24 * 30, + ); + }); + + it('reconciles existing items', async () => { + const oldCache = oldCacheData(); + + packageCache.get.mockResolvedValue(oldCache); + const cache = await DockerHubCache.init(dockerRepository); + const items: DockerHubTag[] = Object.values(oldCache.items); + + const needNextPage = cache.reconcile(items); + + expect(needNextPage).toBe(false); + expect(cache).toEqual({ + cache: oldCache, + dockerRepository: 'foo/bar', + isChanged: false, + }); + + const res = cache.getItems(); + expect(res).toEqual(items); + + await cache.save(); + expect(packageCache.set).not.toHaveBeenCalled(); + }); + + it('reconciles from empty cache', async () => { + const item = newItem(); + const expectedCache = { + items: { + [item.id]: item, + }, + updatedAt: item.last_updated, + }; + const cache = await DockerHubCache.init(dockerRepository); + + const needNextPage = cache.reconcile([item]); + expect(needNextPage).toBe(true); + expect(cache).toEqual({ + cache: expectedCache, + dockerRepository: 'foo/bar', + isChanged: true, + }); + + const res = cache.getItems(); + expect(res).toEqual([item]); + + await cache.save(); + expect(packageCache.set).toHaveBeenCalledWith( + 'datasource-docker-hub-cache', + 'foo/bar', + expectedCache, + 3 * 60 * 24 * 30, + ); + }); +}); diff --git a/lib/modules/datasource/docker/dockerhub-cache.ts b/lib/modules/datasource/docker/dockerhub-cache.ts new file mode 100644 index 00000000000000..0e97726fc01fb2 --- /dev/null +++ b/lib/modules/datasource/docker/dockerhub-cache.ts @@ -0,0 +1,78 @@ +import { dequal } from 'dequal'; +import { DateTime } from 'luxon'; +import * as packageCache from '../../../util/cache/package'; +import type { DockerHubTag } from './schema'; + +export interface DockerHubCacheData { + items: Record; + updatedAt: string | null; +} + +const cacheNamespace = 'datasource-docker-hub-cache'; + +export class DockerHubCache { + private isChanged = false; + + private constructor( + private dockerRepository: string, + private cache: DockerHubCacheData, + ) {} + + static async init(dockerRepository: string): Promise { + let repoCache = await packageCache.get( + cacheNamespace, + dockerRepository, + ); + + repoCache ??= { + items: {}, + updatedAt: null, + }; + + return new DockerHubCache(dockerRepository, repoCache); + } + + reconcile(items: DockerHubTag[]): boolean { + let needNextPage = true; + + let { updatedAt } = this.cache; + let latestDate = updatedAt ? DateTime.fromISO(updatedAt) : null; + + for (const newItem of items) { + const id = newItem.id; + const oldItem = this.cache.items[id]; + + if (dequal(oldItem, newItem)) { + needNextPage = false; + continue; + } + + this.cache.items[newItem.id] = newItem; + const newItemDate = DateTime.fromISO(newItem.last_updated); + if (!latestDate || latestDate < newItemDate) { + updatedAt = newItem.last_updated; + latestDate = newItemDate; + } + + this.isChanged = true; + } + + this.cache.updatedAt = updatedAt; + return needNextPage; + } + + async save(): Promise { + if (this.isChanged) { + await packageCache.set( + cacheNamespace, + this.dockerRepository, + this.cache, + 3 * 60 * 24 * 30, + ); + } + } + + getItems(): DockerHubTag[] { + return Object.values(this.cache.items); + } +} diff --git a/lib/modules/datasource/docker/ecr.ts b/lib/modules/datasource/docker/ecr.ts index 005c7405767b15..f90a6608495a5b 100644 --- a/lib/modules/datasource/docker/ecr.ts +++ b/lib/modules/datasource/docker/ecr.ts @@ -6,7 +6,9 @@ import type { HttpResponse } from '../../../util/http/types'; import { regEx } from '../../../util/regex'; import { addSecretForSanitizing } from '../../../util/sanitize'; -export const ecrRegex = regEx(/\d+\.dkr\.ecr\.([-a-z0-9]+)\.amazonaws\.com/); +export const ecrRegex = regEx( + /\d+\.dkr\.ecr(?:-fips)?\.([-a-z0-9]+)\.amazonaws\.com/, +); export const ecrPublicRegex = regEx(/public\.ecr\.aws/); export async function getECRAuthToken( diff --git a/lib/modules/datasource/docker/index.spec.ts b/lib/modules/datasource/docker/index.spec.ts index bbef484cbd3465..e5805c7ed906e0 100644 --- a/lib/modules/datasource/docker/index.spec.ts +++ b/lib/modules/datasource/docker/index.spec.ts @@ -491,14 +491,16 @@ describe('modules/datasource/docker/index', () => { .scope(gcrUrl) .get('/') .reply(200) - .head('/some-project/some-package/manifests/some-tag') + .head('/google.com/some-project/some-package/manifests/some-tag') .reply(200, '', { 'docker-content-digest': 'some-digest' }); hostRules.find.mockReturnValue({}); const res = await getDigest( { datasource: 'docker', - packageName: 'eu.gcr.io/some-project/some-package', + registryUrl: 'https://eu.gcr.io', + lookupName: 'google.com/some-project/some-package', + packageName: 'eu.gcr.io/google.com/some-project/some-package', }, 'some-tag', ); @@ -742,6 +744,83 @@ describe('modules/datasource/docker/index', () => { ); }); + it('supports architecture-specific digest whithout manifest list', async () => { + const currentDigest = + 'sha256:81c09f6d42c2db8121bcd759565ea244cedc759f36a0f090ec7da9de4f7f8fe4'; + + httpMock + .scope(authUrl) + .get( + '/token?service=registry.docker.io&scope=repository:library/some-dep:pull', + ) + .times(4) + .reply(200, { token: 'some-token' }); + httpMock + .scope(baseUrl) + .get('/') + .times(3) + .reply(401, '', { + 'www-authenticate': + 'Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:library/some-dep:pull"', + }) + .head('/library/some-dep/manifests/' + currentDigest) + .reply(200, '', { + 'content-type': + 'application/vnd.docker.distribution.manifest.v2+json', + }) + .get('/library/some-dep/manifests/' + currentDigest) + .reply(200, { + schemaVersion: 2, + mediaType: 'application/vnd.docker.distribution.manifest.v2+json', + config: { + digest: 'some-config-digest', + mediaType: 'application/vnd.docker.container.image.v1+json', + }, + }) + .get('/library/some-dep/blobs/some-config-digest') + .reply(200, { + architecture: 'amd64', + }); + httpMock + .scope(baseUrl) + .get('/') + .reply(401, '', { + 'www-authenticate': + 'Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:library/some-dep:pull"', + }) + .get('/library/some-dep/manifests/some-new-value') + .reply( + 200, + { + schemaVersion: 2, + mediaType: 'application/vnd.docker.distribution.manifest.v2+json', + config: { + mediaType: 'application/vnd.docker.container.image.v1+json', + size: 2917, + digest: + 'sha256:4591c431eb2fcf90ebb32476db6cfe342617fc3d3ca9653b9e0c47859cac1cf9', + }, + }, + { + 'docker-content-digest': 'some-new-digest', + }, + ); + + const res = await getDigest( + { + datasource: 'docker', + packageName: 'some-dep', + currentDigest, + }, + 'some-new-value', + ); + + expect(logger.logger.debug).toHaveBeenCalledWith( + `Current digest ${currentDigest} relates to architecture amd64`, + ); + expect(res).toBe('some-new-digest'); + }); + it('handles missing architecture-specific digest', async () => { const currentDigest = 'sha256:81c09f6d42c2db8121bcd759565ea244cedc759f36a0f090ec7da9de4f7f8fe4'; @@ -1457,6 +1536,7 @@ describe('modules/datasource/docker/index', () => { packageName: '123456789.dkr.ecr.us-east-1.amazonaws.com/node', }), ).toEqual({ + lookupName: 'node', registryUrl: 'https://123456789.dkr.ecr.us-east-1.amazonaws.com', releases: [], }); @@ -1509,6 +1589,7 @@ describe('modules/datasource/docker/index', () => { packageName: 'public.ecr.aws/amazonlinux/amazonlinux', }), ).toEqual({ + lookupName: 'amazonlinux/amazonlinux', registryUrl: 'https://public.ecr.aws', releases: [], }); @@ -1567,6 +1648,7 @@ describe('modules/datasource/docker/index', () => { packageName: 'ecr-proxy.company.com/node', }), ).toEqual({ + lookupName: 'node', registryUrl: 'https://ecr-proxy.company.com', releases: [], sourceUrl: 'https://github.com/renovatebot/renovate', @@ -1791,21 +1873,25 @@ describe('modules/datasource/docker/index', () => { process.env.RENOVATE_X_DOCKER_HUB_TAGS = 'true'; httpMock .scope(dockerHubUrl) - .get('/library/node/tags?page_size=1000') + .get('/library/node/tags?page_size=1000&ordering=last_updated') .reply(200, { - next: `${dockerHubUrl}/library/node/tags?page=2&page_size=1000`, + next: `${dockerHubUrl}/library/node/tags?page=2&page_size=1000&ordering=last_updated`, results: [ { + id: 2, + last_updated: '2021-01-01T00:00:00.000Z', name: '1.0.0', tag_last_pushed: '2021-01-01T00:00:00.000Z', digest: 'aaa', }, ], }) - .get('/library/node/tags?page=2&page_size=1000') + .get('/library/node/tags?page=2&page_size=1000&ordering=last_updated') .reply(200, { results: [ { + id: 1, + last_updated: '2020-01-01T00:00:00.000Z', name: '0.9.0', tag_last_pushed: '2020-01-01T00:00:00.000Z', digest: 'bbb', @@ -1833,7 +1919,7 @@ describe('modules/datasource/docker/index', () => { const tags = ['1.0.0']; httpMock .scope(dockerHubUrl) - .get('/library/node/tags?page_size=1000') + .get('/library/node/tags?page_size=1000&ordering=last_updated') .reply(404); httpMock .scope(baseUrl) @@ -1861,21 +1947,25 @@ describe('modules/datasource/docker/index', () => { process.env.RENOVATE_X_DOCKER_HUB_TAGS = 'true'; httpMock .scope(dockerHubUrl) - .get('/library/node/tags?page_size=1000') + .get('/library/node/tags?page_size=1000&ordering=last_updated') .reply(200, { - next: `${dockerHubUrl}/library/node/tags?page=2&page_size=1000`, + next: `${dockerHubUrl}/library/node/tags?page=2&page_size=1000&ordering=last_updated`, results: [ { + id: 2, + last_updated: '2021-01-01T00:00:00.000Z', name: '1.0.0', tag_last_pushed: '2021-01-01T00:00:00.000Z', digest: 'aaa', }, ], }) - .get('/library/node/tags?page=2&page_size=1000') + .get('/library/node/tags?page=2&page_size=1000&ordering=last_updated') .reply(200, { results: [ { + id: 1, + last_updated: '2020-01-01T00:00:00.000Z', name: '0.9.0', tag_last_pushed: '2020-01-01T00:00:00.000Z', digest: 'bbb', @@ -2026,6 +2116,7 @@ describe('modules/datasource/docker/index', () => { packageName: 'registry.company.com/node', }); expect(res).toEqual({ + lookupName: 'node', registryUrl: 'https://registry.company.com', releases: [ { @@ -2081,6 +2172,7 @@ describe('modules/datasource/docker/index', () => { packageName: 'registry.company.com/node', }); expect(res).toEqual({ + lookupName: 'node', registryUrl: 'https://registry.company.com', releases: [ { @@ -2144,6 +2236,7 @@ describe('modules/datasource/docker/index', () => { packageName: 'registry.company.com/node', }); expect(res).toEqual({ + lookupName: 'node', registryUrl: 'https://registry.company.com', releases: [], sourceUrl: 'https://github.com/renovatebot/renovate', @@ -2171,6 +2264,7 @@ describe('modules/datasource/docker/index', () => { packageName: 'registry.company.com/node', }); expect(res).toEqual({ + lookupName: 'node', registryUrl: 'https://registry.company.com', releases: [], }); @@ -2195,6 +2289,7 @@ describe('modules/datasource/docker/index', () => { packageName: 'registry.company.com/node', }); expect(res).toEqual({ + lookupName: 'node', registryUrl: 'https://registry.company.com', releases: [], }); @@ -2216,6 +2311,7 @@ describe('modules/datasource/docker/index', () => { packageName: 'registry.company.com/node', }); expect(res).toEqual({ + lookupName: 'node', registryUrl: 'https://registry.company.com', releases: [], }); @@ -2266,6 +2362,7 @@ describe('modules/datasource/docker/index', () => { packageName: 'registry.company.com/node', }); expect(res).toEqual({ + lookupName: 'node', registryUrl: 'https://registry.company.com', releases: [ { @@ -2320,6 +2417,7 @@ describe('modules/datasource/docker/index', () => { packageName: 'registry.company.com/node', }); expect(res).toEqual({ + lookupName: 'node', registryUrl: 'https://registry.company.com', releases: [ { @@ -2350,6 +2448,7 @@ describe('modules/datasource/docker/index', () => { packageName: 'registry.company.com/node', }); expect(res).toEqual({ + lookupName: 'node', registryUrl: 'https://registry.company.com', releases: [], }); @@ -2404,6 +2503,7 @@ describe('modules/datasource/docker/index', () => { packageName: 'registry.company.com/node', }); expect(res).toEqual({ + lookupName: 'node', registryUrl: 'https://registry.company.com', releases: [], }); @@ -2463,6 +2563,7 @@ describe('modules/datasource/docker/index', () => { packageName: 'ghcr.io/visualon/drone-git', }); expect(res).toEqual({ + lookupName: 'visualon/drone-git', registryUrl: 'https://ghcr.io', sourceUrl: 'https://github.com/visualon/drone-git', releases: [{ version: '1.0.0' }], diff --git a/lib/modules/datasource/docker/index.ts b/lib/modules/datasource/docker/index.ts index 0653cc22f6f4b4..c0266ac7a09a03 100644 --- a/lib/modules/datasource/docker/index.ts +++ b/lib/modules/datasource/docker/index.ts @@ -37,6 +37,7 @@ import { sourceLabel, sourceLabels, } from './common'; +import { DockerHubCache } from './dockerhub-cache'; import { ecrPublicRegex, ecrRegex, isECRMaxResultsError } from './ecr'; import { DistributionManifest, @@ -80,6 +81,13 @@ export class DockerDatasource extends Datasource { override readonly defaultConfig = defaultConfig; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `tag_last_pushed` field in thre results.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined from the `org.opencontainers.image.source` and `org.label-schema.vcs-url` labels present in the metadata of the **latest stable** image found on the Docker registry.'; + constructor() { super(DockerDatasource.id); } @@ -291,7 +299,14 @@ export class DockerDatasource extends Datasource { const parsed = ManifestJson.safeParse(manifestResponse.body); if (!parsed.success) { logger.debug( - { registry, dockerRepository, tag, err: parsed.error }, + { + registry, + dockerRepository, + tag, + body: manifestResponse.body, + headers: manifestResponse.headers, + err: parsed.error, + }, 'Invalid manifest response', ); return null; @@ -788,13 +803,22 @@ export class DockerDatasource extends Datasource { }, }) override async getDigest( - { registryUrl, packageName, currentDigest }: DigestConfig, + { registryUrl, lookupName, packageName, currentDigest }: DigestConfig, newValue?: string, ): Promise { - const { registryHost, dockerRepository } = getRegistryRepository( - packageName, - registryUrl!, - ); + let registryHost: string; + let dockerRepository: string; + if (registryUrl && lookupName) { + // Reuse the resolved values from getReleases() + registryHost = registryUrl; + dockerRepository = lookupName; + } else { + // Resolve values independently + ({ registryHost, dockerRepository } = getRegistryRepository( + packageName, + registryUrl!, + )); + } logger.debug( // TODO: types (#22198) `getDigest(${registryHost}, ${dockerRepository}, ${newValue})`, @@ -846,23 +870,45 @@ export class DockerDatasource extends Datasource { ); if (architecture && manifestResponse) { - const parse = ManifestJson.safeParse(manifestResponse.body); - const manifestList = parse.success - ? parse.data - : /* istanbul ignore next: hard to test */ null; - if ( - manifestList && - (manifestList.mediaType === - 'application/vnd.docker.distribution.manifest.list.v2+json' || + const parsed = ManifestJson.safeParse(manifestResponse.body); + /* istanbul ignore else: hard to test */ + if (parsed.success) { + const manifestList = parsed.data; + if ( + manifestList.mediaType === + 'application/vnd.docker.distribution.manifest.list.v2+json' || manifestList.mediaType === - 'application/vnd.oci.image.index.v1+json') - ) { - for (const manifest of manifestList.manifests) { - if (manifest.platform?.architecture === architecture) { - digest = manifest.digest; - break; + 'application/vnd.oci.image.index.v1+json' + ) { + for (const manifest of manifestList.manifests) { + if (manifest.platform?.architecture === architecture) { + digest = manifest.digest; + break; + } } + // TODO: return null if no matching architecture digest found + // https://github.com/renovatebot/renovate/discussions/22639 + } else if ( + hasKey('docker-content-digest', manifestResponse.headers) + ) { + // TODO: return null if no matching architecture, requires to fetch the config manifest + // https://github.com/renovatebot/renovate/discussions/22639 + digest = manifestResponse.headers[ + 'docker-content-digest' + ] as string; } + } else { + logger.debug( + { + registryHost, + dockerRepository, + newTag, + body: manifestResponse.body, + headers: manifestResponse.headers, + err: parsed.error, + }, + 'Failed to parse manifest response', + ); } } @@ -918,10 +964,11 @@ export class DockerDatasource extends Datasource { key: (dockerRepository: string) => `${dockerRepository}`, }) async getDockerHubTags(dockerRepository: string): Promise { - const result: Release[] = []; - let url: null | string = - `https://hub.docker.com/v2/repositories/${dockerRepository}/tags?page_size=1000`; - while (url) { + let url = `https://hub.docker.com/v2/repositories/${dockerRepository}/tags?page_size=1000&ordering=last_updated`; + + const cache = await DockerHubCache.init(dockerRepository); + let needNextPage: boolean = true; + while (needNextPage) { const { val, err } = await this.http .getJsonSafe(url, DockerHubTagsPage) .unwrap(); @@ -931,11 +978,39 @@ export class DockerDatasource extends Datasource { return null; } - result.push(...val.items); - url = val.nextPage; + const { results, next } = val; + + needNextPage = cache.reconcile(results); + + if (!next) { + break; + } + + url = next; } - return result; + await cache.save(); + + const items = cache.getItems(); + return items.map( + ({ + name: version, + tag_last_pushed: releaseTimestamp, + digest: newDigest, + }) => { + const release: Release = { version }; + + if (releaseTimestamp) { + release.releaseTimestamp = releaseTimestamp; + } + + if (newDigest) { + release.newDigest = newDigest; + } + + return release; + }, + ); } /** @@ -1006,6 +1081,10 @@ export class DockerDatasource extends Datasource { registryUrl: registryHost, releases, }; + if (dockerRepository !== packageName) { + // This will be reused later if a getDigest() call is made + ret.lookupName = dockerRepository; + } const tags = releases.map((release) => release.version); const latestTag = tags.includes('latest') diff --git a/lib/modules/datasource/docker/schema.ts b/lib/modules/datasource/docker/schema.ts index 1af87da24b49d4..6a58e08abc513a 100644 --- a/lib/modules/datasource/docker/schema.ts +++ b/lib/modules/datasource/docker/schema.ts @@ -1,7 +1,6 @@ import { z } from 'zod'; import { logger } from '../../../logger'; import { Json, LooseArray } from '../../../util/schema-utils'; -import type { Release } from '../types'; // OCI manifests @@ -155,39 +154,23 @@ export const Manifest = ManifestObject.passthrough() export type Manifest = z.infer; export const ManifestJson = Json.pipe(Manifest); -export const DockerHubTag = z - .object({ - name: z.string(), - tag_last_pushed: z.string().datetime().nullable().catch(null), - digest: z.string().nullable().catch(null), - }) - .transform(({ name, tag_last_pushed, digest }) => { - const release: Release = { version: name }; - - if (tag_last_pushed) { - release.releaseTimestamp = tag_last_pushed; - } - - if (digest) { - release.newDigest = digest; - } - - return release; - }); - -export const DockerHubTagsPage = z - .object({ - next: z.string().nullable().catch(null), - results: LooseArray(DockerHubTag, { - onError: /* istanbul ignore next */ ({ error }) => { - logger.debug( - { error }, - 'Docker: Failed to parse some tags from Docker Hub', - ); - }, - }), - }) - .transform(({ next, results }) => ({ - nextPage: next, - items: results, - })); +export const DockerHubTag = z.object({ + id: z.number(), + last_updated: z.string().datetime(), + name: z.string(), + tag_last_pushed: z.string().datetime().nullable().catch(null), + digest: z.string().nullable().catch(null), +}); +export type DockerHubTag = z.infer; + +export const DockerHubTagsPage = z.object({ + next: z.string().nullable().catch(null), + results: LooseArray(DockerHubTag, { + onError: /* istanbul ignore next */ ({ error }) => { + logger.debug( + { error }, + 'Docker: Failed to parse some tags from Docker Hub', + ); + }, + }), +}); diff --git a/lib/modules/datasource/dotnet-version/index.spec.ts b/lib/modules/datasource/dotnet-version/index.spec.ts index e41ddf85995e30..84ed99c2e9d495 100644 --- a/lib/modules/datasource/dotnet-version/index.spec.ts +++ b/lib/modules/datasource/dotnet-version/index.spec.ts @@ -128,16 +128,31 @@ describe('modules/datasource/dotnet-version/index', () => { expect(res?.sourceUrl).toBe('https://github.com/dotnet/sdk'); expect(res?.releases).toHaveLength(19); expect(res?.releases).toIncludeAllPartialMembers([ - { version: '3.1.100-preview1-014459' }, - { version: '3.1.423' }, - { version: '5.0.100-preview.1.20155.7' }, - { version: '5.0.408' }, - { version: '6.0.100-preview.1.21103.13' }, - { version: '6.0.401' }, - { version: '6.0.304' }, - { version: '6.0.109' }, - { version: '7.0.100-preview.1.22110.4' }, - { version: '7.0.100-rc.1.22431.12' }, + { + version: '3.1.100-preview1-014459', + releaseTimestamp: '2019-10-15T00:00:00.000Z', + }, + { version: '3.1.423', releaseTimestamp: '2022-09-13T00:00:00.000Z' }, + { + version: '5.0.100-preview.1.20155.7', + releaseTimestamp: '2020-03-16T00:00:00.000Z', + }, + { version: '5.0.408', releaseTimestamp: '2022-05-10T00:00:00.000Z' }, + { + version: '6.0.100-preview.1.21103.13', + releaseTimestamp: '2021-02-17T00:00:00.000Z', + }, + { version: '6.0.401', releaseTimestamp: '2022-09-13T00:00:00.000Z' }, + { version: '6.0.304', releaseTimestamp: '2022-09-13T00:00:00.000Z' }, + { version: '6.0.109', releaseTimestamp: '2022-09-13T00:00:00.000Z' }, + { + version: '7.0.100-preview.1.22110.4', + releaseTimestamp: '2022-02-17T00:00:00.000Z', + }, + { + version: '7.0.100-rc.1.22431.12', + releaseTimestamp: '2022-09-14T00:00:00.000Z', + }, ]); }); @@ -164,14 +179,29 @@ describe('modules/datasource/dotnet-version/index', () => { expect(res?.sourceUrl).toBe('https://github.com/dotnet/runtime'); expect(res?.releases).toHaveLength(17); expect(res?.releases).toIncludeAllPartialMembers([ - { version: '3.1.0-preview1.19506.1' }, - { version: '3.1.29' }, - { version: '5.0.0-preview.1.20120.5' }, - { version: '5.0.17' }, - { version: '6.0.0-preview.1.21102.12' }, - { version: '6.0.9' }, - { version: '7.0.0-preview.1.22076.8' }, - { version: '7.0.0-rc.1.22426.10' }, + { + version: '3.1.0-preview1.19506.1', + releaseTimestamp: '2019-10-15T00:00:00.000Z', + }, + { version: '3.1.29', releaseTimestamp: '2022-09-13T00:00:00.000Z' }, + { + version: '5.0.0-preview.1.20120.5', + releaseTimestamp: '2020-03-16T00:00:00.000Z', + }, + { version: '5.0.17', releaseTimestamp: '2022-05-10T00:00:00.000Z' }, + { + version: '6.0.0-preview.1.21102.12', + releaseTimestamp: '2021-02-17T00:00:00.000Z', + }, + { version: '6.0.9', releaseTimestamp: '2022-09-13T00:00:00.000Z' }, + { + version: '7.0.0-preview.1.22076.8', + releaseTimestamp: '2022-02-17T00:00:00.000Z', + }, + { + version: '7.0.0-rc.1.22426.10', + releaseTimestamp: '2022-09-14T00:00:00.000Z', + }, ]); }); }); diff --git a/lib/modules/datasource/dotnet-version/index.ts b/lib/modules/datasource/dotnet-version/index.ts index c7051630d6223c..f5a799dc5f123f 100644 --- a/lib/modules/datasource/dotnet-version/index.ts +++ b/lib/modules/datasource/dotnet-version/index.ts @@ -23,6 +23,13 @@ export class DotnetVersionDatasource extends Datasource { 'https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/releases-index.json', ]; + override releaseTimestampSupport = true; + override releaseTimestampNote = + 'The release timestamp is determined from the `release-date` field in the results.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'We use the URL https://github.com/dotnet/sdk for the `dotnet-sdk` package and, the https://github.com/dotnet/runtime URL for the `dotnet-runtime` package.'; + @cache({ namespace: `datasource-${DotnetVersionDatasource.id}`, key: ({ packageName }: GetReleasesConfig) => packageName, diff --git a/lib/modules/datasource/endoflife-date/index.ts b/lib/modules/datasource/endoflife-date/index.ts index 036a9f3e719635..630129d977ba99 100644 --- a/lib/modules/datasource/endoflife-date/index.ts +++ b/lib/modules/datasource/endoflife-date/index.ts @@ -14,6 +14,10 @@ export class EndoflifeDatePackagesource extends Datasource { override readonly caching = true; override readonly defaultVersioning = 'loose'; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `releaseDate` field in the results.'; + constructor() { super(EndoflifeDatePackagesource.id); } diff --git a/lib/modules/datasource/flutter-version/index.ts b/lib/modules/datasource/flutter-version/index.ts index bead276e7a3f48..46dd2ce47e8d44 100644 --- a/lib/modules/datasource/flutter-version/index.ts +++ b/lib/modules/datasource/flutter-version/index.ts @@ -21,6 +21,13 @@ export class FlutterVersionDatasource extends Datasource { override readonly defaultVersioning = semverId; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `release_date` field in the results.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'We use the URL: https://github.com/flutter/flutter.'; + async getReleases({ registryUrl, }: GetReleasesConfig): Promise { diff --git a/lib/modules/datasource/galaxy-collection/index.ts b/lib/modules/datasource/galaxy-collection/index.ts index 3fd7e63ace7b37..ec109bc450a7a5 100644 --- a/lib/modules/datasource/galaxy-collection/index.ts +++ b/lib/modules/datasource/galaxy-collection/index.ts @@ -28,6 +28,15 @@ export class GalaxyCollectionDatasource extends Datasource { override readonly defaultVersioning = pep440Versioning.id; + override readonly releaseTimestampSupport = true; + override releaseTimestampNote = + 'The release timestamp is determined from the `created_at` field in the results.'; + // sourceUrl is returned in each release as well as the ReleaseResult + // the one present in release result is the sourceUrl of the latest release + override readonly sourceUrlSupport = 'release'; + override readonly sourceUrlNote = + 'The `sourceUrl` is determined from the `repository` field in the results.'; + @cache({ namespace: `datasource-${GalaxyCollectionDatasource.id}`, key: ({ packageName }: GetReleasesConfig) => packageName, diff --git a/lib/modules/datasource/galaxy/index.ts b/lib/modules/datasource/galaxy/index.ts index 698d9ede78f73c..64e0412f51cc2b 100644 --- a/lib/modules/datasource/galaxy/index.ts +++ b/lib/modules/datasource/galaxy/index.ts @@ -19,6 +19,13 @@ export class GalaxyDatasource extends Datasource { override readonly defaultVersioning = pep440Versioning.id; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `created` field in the results.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined from the `github_user` and `github_repo` fields in the results.'; + @cache({ namespace: 'datasource-galaxy', key: (getReleasesConfig: GetReleasesConfig) => diff --git a/lib/modules/datasource/git-refs/index.ts b/lib/modules/datasource/git-refs/index.ts index d6f93ba1a12753..a5271f9e02c68a 100644 --- a/lib/modules/datasource/git-refs/index.ts +++ b/lib/modules/datasource/git-refs/index.ts @@ -17,6 +17,10 @@ export class GitRefsDatasource extends GitDatasource { override readonly customRegistrySupport = false; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined by using the `packageName` and `registryUrl`.'; + @cache({ namespace: `datasource-${GitRefsDatasource.id}`, key: ({ packageName }: GetReleasesConfig) => packageName, @@ -51,7 +55,7 @@ export class GitRefsDatasource extends GitDatasource { releases: uniqueRefs.map((ref) => ({ version: ref, gitRef: ref, - newDigest: rawRefs!.find((rawRef) => rawRef.value === ref)?.hash, + newDigest: rawRefs.find((rawRef) => rawRef.value === ref)?.hash, })), }; diff --git a/lib/modules/datasource/git-tags/index.ts b/lib/modules/datasource/git-tags/index.ts index 09f084d09b345e..d5300c7ba4ba63 100644 --- a/lib/modules/datasource/git-tags/index.ts +++ b/lib/modules/datasource/git-tags/index.ts @@ -11,6 +11,9 @@ export class GitTagsDatasource extends GitDatasource { } override readonly customRegistrySupport = false; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined by using the `packageName` and `registryUrl`.'; @cache({ namespace: `datasource-${GitTagsDatasource.id}`, diff --git a/lib/modules/datasource/gitea-releases/index.ts b/lib/modules/datasource/gitea-releases/index.ts index 66d41f61c11ca9..a2febc4d922b28 100644 --- a/lib/modules/datasource/gitea-releases/index.ts +++ b/lib/modules/datasource/gitea-releases/index.ts @@ -16,6 +16,13 @@ export class GiteaReleasesDatasource extends Datasource { private static readonly cacheNamespace: PackageCacheNamespace = `datasource-${GiteaReleasesDatasource.id}`; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `published_at` field in the results.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined by using the `packageName` and `registryUrl`.'; + constructor() { super(GiteaReleasesDatasource.id); } diff --git a/lib/modules/datasource/gitea-tags/index.ts b/lib/modules/datasource/gitea-tags/index.ts index d177a50f560e41..fb893b0c33c59a 100644 --- a/lib/modules/datasource/gitea-tags/index.ts +++ b/lib/modules/datasource/gitea-tags/index.ts @@ -16,6 +16,13 @@ export class GiteaTagsDatasource extends Datasource { private static readonly cacheNamespace: PackageCacheNamespace = `datasource-${GiteaTagsDatasource.id}`; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `created` field in the results.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined by using the `packageName` and `registryUrl`.'; + constructor() { super(GiteaTagsDatasource.id); } diff --git a/lib/modules/datasource/github-release-attachments/index.ts b/lib/modules/datasource/github-release-attachments/index.ts index d79242124e5acb..da41d74df40512 100644 --- a/lib/modules/datasource/github-release-attachments/index.ts +++ b/lib/modules/datasource/github-release-attachments/index.ts @@ -38,6 +38,14 @@ export class GithubReleaseAttachmentsDatasource extends Datasource { override http: GithubHttp; + override readonly releaseTimestampSupport = true; + // Note: not sure + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `releaseTimestamp` field in the results.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined by using the `packageName` and `registryUrl`.'; + constructor() { super(GithubReleaseAttachmentsDatasource.id); this.http = new GithubHttp(GithubReleaseAttachmentsDatasource.id); @@ -222,7 +230,7 @@ export class GithubReleaseAttachmentsDatasource extends Datasource { } /** - * This function can be used to fetch releases with a customisable versioning + * This function can be used to fetch releases with a customizable versioning * (e.g. semver) and with releases. * * This function will: diff --git a/lib/modules/datasource/github-releases/index.ts b/lib/modules/datasource/github-releases/index.ts index b13d0e15000eed..1963f34f4089bd 100644 --- a/lib/modules/datasource/github-releases/index.ts +++ b/lib/modules/datasource/github-releases/index.ts @@ -21,6 +21,14 @@ export class GithubReleasesDatasource extends Datasource { override http: GithubHttp; + override readonly releaseTimestampSupport = true; + // Note: not sure + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `releaseTimestamp` field from the response.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined by using the `packageName` and `registryUrl`.'; + constructor() { super(GithubReleasesDatasource.id); this.http = new GithubHttp(GithubReleasesDatasource.id); diff --git a/lib/modules/datasource/github-runners/index.spec.ts b/lib/modules/datasource/github-runners/index.spec.ts index 0875f1dabfc9ab..aa2bcbd3e8effe 100644 --- a/lib/modules/datasource/github-runners/index.spec.ts +++ b/lib/modules/datasource/github-runners/index.spec.ts @@ -15,6 +15,7 @@ describe('modules/datasource/github-runners/index', () => { { version: '18.04', isDeprecated: true }, { version: '20.04' }, { version: '22.04' }, + { version: '24.04', isStable: false }, ], sourceUrl: 'https://github.com/actions/runner-images', }); @@ -35,9 +36,9 @@ describe('modules/datasource/github-runners/index', () => { { version: '13-xlarge' }, { version: '13-large' }, { version: '13' }, - { version: '14-xlarge', isStable: false }, - { version: '14-large', isStable: false }, - { version: '14', isStable: false }, + { version: '14-xlarge' }, + { version: '14-large' }, + { version: '14' }, ], sourceUrl: 'https://github.com/actions/runner-images', }); diff --git a/lib/modules/datasource/github-runners/index.ts b/lib/modules/datasource/github-runners/index.ts index 9748f5db90c495..d77eda75b178f3 100644 --- a/lib/modules/datasource/github-runners/index.ts +++ b/lib/modules/datasource/github-runners/index.ts @@ -5,20 +5,25 @@ import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; export class GithubRunnersDatasource extends Datasource { static readonly id = 'github-runners'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'We use the URL: https://github.com/actions/runner-images.'; + /** * Only add stable runners to the datasource. See datasource readme for details. */ private static readonly releases: Record = { ubuntu: [ + { version: '24.04', isStable: false }, { version: '22.04' }, { version: '20.04' }, { version: '18.04', isDeprecated: true }, { version: '16.04', isDeprecated: true }, ], macos: [ - { version: '14', isStable: false }, - { version: '14-large', isStable: false }, - { version: '14-xlarge', isStable: false }, + { version: '14' }, + { version: '14-large' }, + { version: '14-xlarge' }, { version: '13' }, { version: '13-large' }, { version: '13-xlarge' }, diff --git a/lib/modules/datasource/github-runners/readme.md b/lib/modules/datasource/github-runners/readme.md index 1cc9b6f758ec9a..4c3d92bb3249e9 100644 --- a/lib/modules/datasource/github-runners/readme.md +++ b/lib/modules/datasource/github-runners/readme.md @@ -1,7 +1,7 @@ This datasource returns a list of all runners that are hosted by GitHub. The datasource is based on [GitHub's `runner-images` repository](https://github.com/actions/runner-images). -Examples: `windows-2022` / `ubuntu-22.04` / `macos-13` +Examples: `windows-2022` / `ubuntu-24.04` / `macos-14` ## Maintenance diff --git a/lib/modules/datasource/github-tags/index.ts b/lib/modules/datasource/github-tags/index.ts index 328374cb91db5d..0fdca9b6b3020d 100644 --- a/lib/modules/datasource/github-tags/index.ts +++ b/lib/modules/datasource/github-tags/index.ts @@ -20,6 +20,14 @@ export class GithubTagsDatasource extends Datasource { override readonly registryStrategy = 'hunt'; + override readonly releaseTimestampSupport = true; + // Note: not sure + override readonly releaseTimestampNote = + 'The get release timestamp is determined from the `releaseTimestamp` field in the results.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined by using the `packageName` and `registryUrl`.'; + override http: GithubHttp; constructor() { diff --git a/lib/modules/datasource/gitlab-packages/index.ts b/lib/modules/datasource/gitlab-packages/index.ts index e30bc997a3e0c3..28d0483aae5069 100644 --- a/lib/modules/datasource/gitlab-packages/index.ts +++ b/lib/modules/datasource/gitlab-packages/index.ts @@ -19,6 +19,10 @@ export class GitlabPackagesDatasource extends Datasource { override defaultRegistryUrls = ['https://gitlab.com']; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `created_at` field in the results.'; + constructor() { super(datasource); this.http = new GitlabHttp(datasource); diff --git a/lib/modules/datasource/gitlab-releases/index.ts b/lib/modules/datasource/gitlab-releases/index.ts index a6458d35e02ff2..54ba562796a795 100644 --- a/lib/modules/datasource/gitlab-releases/index.ts +++ b/lib/modules/datasource/gitlab-releases/index.ts @@ -11,6 +11,13 @@ export class GitlabReleasesDatasource extends Datasource { static readonly registryStrategy = 'first'; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `released_at` field in the results.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined by using the `packageName` and `registryUrl`.'; + constructor() { super(GitlabReleasesDatasource.id); this.http = new GitlabHttp(GitlabReleasesDatasource.id); diff --git a/lib/modules/datasource/gitlab-tags/index.ts b/lib/modules/datasource/gitlab-tags/index.ts index d1317c5f803569..247b4d5e69bfe8 100644 --- a/lib/modules/datasource/gitlab-tags/index.ts +++ b/lib/modules/datasource/gitlab-tags/index.ts @@ -12,6 +12,13 @@ export class GitlabTagsDatasource extends Datasource { protected override http: GitlabHttp; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'To get release timestamp we use the `created_at` field from the response.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined by using the `packageName` and `registryUrl`.'; + constructor() { super(GitlabTagsDatasource.id); this.http = new GitlabHttp(GitlabTagsDatasource.id); diff --git a/lib/modules/datasource/go/base.ts b/lib/modules/datasource/go/base.ts index 3bde8e040f1ab3..f963490eebf5a6 100644 --- a/lib/modules/datasource/go/base.ts +++ b/lib/modules/datasource/go/base.ts @@ -255,7 +255,7 @@ export class BaseGoDatasource { if (datasource !== null) { return datasource; } - // fall back to old behaviour if detection did not work + // fall back to old behavior if detection did not work switch (detectPlatform(goImportURL)) { case 'github': { diff --git a/lib/modules/datasource/go/goproxy-parser.spec.ts b/lib/modules/datasource/go/goproxy-parser.spec.ts new file mode 100644 index 00000000000000..3dc0cd067f6e5e --- /dev/null +++ b/lib/modules/datasource/go/goproxy-parser.spec.ts @@ -0,0 +1,135 @@ +import * as memCache from '../../../util/cache/memory'; +import { parseGoproxy, parseNoproxy } from './goproxy-parser'; + +describe('modules/datasource/go/goproxy-parser', () => { + beforeEach(() => { + memCache.init(); + }); + + describe('parseGoproxy', () => { + it('parses single url', () => { + const result = parseGoproxy('foo'); + expect(result).toMatchObject([{ url: 'foo' }]); + }); + + it('parses multiple urls', () => { + const result = parseGoproxy('foo,bar|baz,qux'); + expect(result).toMatchObject([ + { url: 'foo', fallback: ',' }, + { url: 'bar', fallback: '|' }, + { url: 'baz', fallback: ',' }, + { url: 'qux' }, + ]); + }); + + it('ignores everything starting from "direct" and "off" keywords', () => { + expect(parseGoproxy(undefined)).toBeEmpty(); + expect(parseGoproxy(undefined)).toBeEmpty(); + expect(parseGoproxy('')).toBeEmpty(); + expect(parseGoproxy('off')).toMatchObject([ + { url: 'off', fallback: '|' }, + ]); + expect(parseGoproxy('direct')).toMatchObject([ + { url: 'direct', fallback: '|' }, + ]); + expect(parseGoproxy('foo,off|direct,qux')).toMatchObject([ + { url: 'foo', fallback: ',' }, + { url: 'off', fallback: '|' }, + { url: 'direct', fallback: ',' }, + { url: 'qux', fallback: '|' }, + ]); + }); + + it('caches results', () => { + expect(parseGoproxy('foo,bar')).toBe(parseGoproxy('foo,bar')); + }); + }); + + describe('parseNoproxy', () => { + it('produces regex', () => { + expect(parseNoproxy(undefined)).toBeNull(); + expect(parseNoproxy(null)).toBeNull(); + expect(parseNoproxy('')).toBeNull(); + expect(parseNoproxy('/')).toBeNull(); + expect(parseNoproxy('*')?.source).toBe('^(?:[^\\/]*)(?:\\/.*)?$'); + expect(parseNoproxy('?')?.source).toBe('^(?:[^\\/])(?:\\/.*)?$'); + expect(parseNoproxy('foo')?.source).toBe('^(?:foo)(?:\\/.*)?$'); + expect(parseNoproxy('\\f\\o\\o')?.source).toBe('^(?:foo)(?:\\/.*)?$'); + expect(parseNoproxy('foo,bar')?.source).toBe('^(?:foo|bar)(?:\\/.*)?$'); + expect(parseNoproxy('[abc]')?.source).toBe('^(?:[abc])(?:\\/.*)?$'); + expect(parseNoproxy('[a-c]')?.source).toBe('^(?:[a-c])(?:\\/.*)?$'); + expect(parseNoproxy('[\\a-\\c]')?.source).toBe('^(?:[a-c])(?:\\/.*)?$'); + expect(parseNoproxy('a.b.c')?.source).toBe('^(?:a\\.b\\.c)(?:\\/.*)?$'); + expect(parseNoproxy('trailing/')?.source).toBe( + '^(?:trailing)(?:\\/.*)?$', + ); + }); + + it('matches on real package prefixes', () => { + expect(parseNoproxy('ex.co')?.test('ex.co/foo')).toBeTrue(); + expect(parseNoproxy('ex.co/')?.test('ex.co/foo')).toBeTrue(); + expect(parseNoproxy('ex.co/foo/bar')?.test('ex.co/foo/bar')).toBeTrue(); + expect(parseNoproxy('ex.co/foo/bar')?.test('ex.co/foo/bar')).toBeTrue(); + expect(parseNoproxy('*/foo/*')?.test('example.com/foo/bar')).toBeTrue(); + expect(parseNoproxy('ex.co/foo/*')?.test('ex.co/foo/bar')).toBeTrue(); + expect(parseNoproxy('ex.co/foo/*')?.test('ex.co/foo/baz')).toBeTrue(); + expect(parseNoproxy('ex.co')?.test('ex.co/foo/v2')).toBeTrue(); + + expect(parseNoproxy('ex.co/foo/bar')?.test('ex.co/foo/bar')).toBeTrue(); + expect(parseNoproxy('*/foo/*')?.test('example.com/foo/bar')).toBeTrue(); + expect(parseNoproxy('ex.co/foo/*')?.test('ex.co/foo/bar')).toBeTrue(); + expect(parseNoproxy('ex.co/foo/*')?.test('ex.co/foo/baz')).toBeTrue(); + expect( + parseNoproxy('ex.co/foo/bar,ex.co/foo/baz')?.test('ex.co/foo/bar'), + ).toBeTrue(); + expect( + parseNoproxy('ex.co/foo/bar,ex.co/foo/baz')?.test('ex.co/foo/baz'), + ).toBeTrue(); + expect( + parseNoproxy('ex.co/foo/bar,ex.co/foo/baz')?.test('ex.co/foo/qux'), + ).toBeFalse(); + + expect(parseNoproxy('ex')?.test('ex.co/foo')).toBeFalse(); + + expect(parseNoproxy('aba')?.test('x/aba')).toBeFalse(); + expect(parseNoproxy('x/b')?.test('x/aba')).toBeFalse(); + expect(parseNoproxy('x/ab')?.test('x/aba')).toBeFalse(); + expect(parseNoproxy('x/ab[a-b]')?.test('x/aba')).toBeTrue(); + }); + + it('matches on wildcards', () => { + expect(parseNoproxy('/*/')?.test('ex.co/foo')).toBeFalse(); + expect(parseNoproxy('*/foo')?.test('ex.co/foo')).toBeTrue(); + expect(parseNoproxy('*/fo')?.test('ex.co/foo')).toBeFalse(); + expect(parseNoproxy('*/fo?')?.test('ex.co/foo')).toBeTrue(); + expect(parseNoproxy('*/fo*')?.test('ex.co/foo')).toBeTrue(); + expect(parseNoproxy('*fo*')?.test('ex.co/foo')).toBeFalse(); + + expect(parseNoproxy('*.co')?.test('ex.co/foo')).toBeTrue(); + expect(parseNoproxy('ex*')?.test('ex.co/foo')).toBeTrue(); + expect(parseNoproxy('*/foo')?.test('ex.co/foo/v2')).toBeTrue(); + expect(parseNoproxy('*/foo/')?.test('ex.co/foo/v2')).toBeTrue(); + expect(parseNoproxy('*/foo/*')?.test('ex.co/foo/v2')).toBeTrue(); + expect(parseNoproxy('*/foo/*/')?.test('ex.co/foo/v2')).toBeTrue(); + expect(parseNoproxy('*/v2')?.test('ex.co/foo/v2')).toBeFalse(); + expect(parseNoproxy('*/*/v2')?.test('ex.co/foo/v2')).toBeTrue(); + expect(parseNoproxy('*/*/*')?.test('ex.co/foo/v2')).toBeTrue(); + expect(parseNoproxy('*/*/*/')?.test('ex.co/foo/v2')).toBeTrue(); + expect(parseNoproxy('*/*/*')?.test('ex.co/foo')).toBeFalse(); + expect(parseNoproxy('*/*/*/')?.test('ex.co/foo')).toBeFalse(); + + expect(parseNoproxy('*/*/*,,')?.test('ex.co/repo')).toBeFalse(); + expect(parseNoproxy('*/*/*,,*/repo')?.test('ex.co/repo')).toBeTrue(); + expect(parseNoproxy(',,*/repo')?.test('ex.co/repo')).toBeTrue(); + }); + + it('matches on character ranges', () => { + expect(parseNoproxy('x/ab[a-b]')?.test('x/aba')).toBeTrue(); + expect(parseNoproxy('x/ab[a-b]')?.test('x/abc')).toBeFalse(); + }); + + it('caches results', () => { + expect(parseNoproxy('foo/bar')).toBe(parseNoproxy('foo/bar')); + }); + }); +}); diff --git a/lib/modules/datasource/go/goproxy-parser.ts b/lib/modules/datasource/go/goproxy-parser.ts new file mode 100644 index 00000000000000..78f8bdd4feae4a --- /dev/null +++ b/lib/modules/datasource/go/goproxy-parser.ts @@ -0,0 +1,115 @@ +import is from '@sindresorhus/is'; +import moo from 'moo'; +import * as memCache from '../../../util/cache/memory'; +import { regEx } from '../../../util/regex'; +import type { GoproxyItem } from './types'; + +/** + * Parse `GOPROXY` to the sequence of url + fallback strategy tags. + * + * @example + * parseGoproxy('foo.example.com|bar.example.com,baz.example.com') + * // [ + * // { url: 'foo.example.com', fallback: '|' }, + * // { url: 'bar.example.com', fallback: ',' }, + * // { url: 'baz.example.com', fallback: '|' }, + * // ] + * + * @see https://golang.org/ref/mod#goproxy-protocol + */ +export function parseGoproxy( + input: string | undefined = process.env.GOPROXY, +): GoproxyItem[] { + if (!is.string(input)) { + return []; + } + + const cacheKey = `goproxy::${input}`; + const cachedResult = memCache.get(cacheKey); + if (cachedResult) { + return cachedResult; + } + + const result: GoproxyItem[] = input + .split(regEx(/([^,|]*(?:,|\|))/)) + .filter(Boolean) + .map((s) => s.split(/(?=,|\|)/)) // TODO: #12872 lookahead + .map(([url, separator]) => ({ + url, + fallback: separator === ',' ? ',' : '|', + })); + + memCache.set(cacheKey, result); + return result; +} + +// https://golang.org/pkg/path/#Match +const noproxyLexer = moo.states({ + main: { + separator: { + match: /\s*?,\s*?/, // TODO #12870 + value: (_: string) => '|', + }, + asterisk: { + match: '*', + value: (_: string) => '[^/]*', + }, + qmark: { + match: '?', + value: (_: string) => '[^/]', + }, + characterRangeOpen: { + match: '[', + push: 'characterRange', + value: (_: string) => '[', + }, + trailingSlash: { + match: /\/$/, + value: (_: string) => '', + }, + char: { + match: /[^*?\\[\n]/, + value: (s: string) => s.replace(regEx('\\.', 'g'), '\\.'), + }, + escapedChar: { + match: /\\./, // TODO #12870 + value: (s: string) => s.slice(1), + }, + }, + characterRange: { + char: /[^\\\]\n]/, // TODO #12870 + escapedChar: { + match: /\\./, // TODO #12870 + value: (s: string) => s.slice(1), + }, + characterRangeEnd: { + match: ']', + pop: 1, + }, + }, +}); + +export function parseNoproxy( + input: unknown = process.env.GONOPROXY ?? process.env.GOPRIVATE, +): RegExp | null { + if (!is.string(input)) { + return null; + } + + const cacheKey = `noproxy::${input}`; + const cachedResult = memCache.get(cacheKey); + if (cachedResult !== undefined) { + return cachedResult; + } + + const noproxyPattern = [...noproxyLexer.reset(input)] + .map(({ value }) => value) + .join(''); + + const result = noproxyPattern + ? regEx(`^(?:${noproxyPattern})(?:/.*)?$`) + : null; + + memCache.set(cacheKey, result); + return result; +} diff --git a/lib/modules/datasource/go/index.spec.ts b/lib/modules/datasource/go/index.spec.ts index dad705a914dc18..18c3d831ab775c 100644 --- a/lib/modules/datasource/go/index.spec.ts +++ b/lib/modules/datasource/go/index.spec.ts @@ -193,5 +193,20 @@ describe('modules/datasource/go/index', () => { expect(res).not.toBeNull(); expect(res).toBeDefined(); }); + + describe('GOPROXY', () => { + afterEach(() => { + delete process.env.GOPROXY; + }); + + it('returns null when GOPROXY contains off', async () => { + process.env.GOPROXY = 'https://proxy.golang.org,off'; + const res = await datasource.getDigest( + { packageName: 'golang.org/x/text' }, + 'v1.2.3', + ); + expect(res).toBeNull(); + }); + }); }); }); diff --git a/lib/modules/datasource/go/index.ts b/lib/modules/datasource/go/index.ts index 48702e2d2090b9..a3abb57cf753b9 100644 --- a/lib/modules/datasource/go/index.ts +++ b/lib/modules/datasource/go/index.ts @@ -1,4 +1,5 @@ import is from '@sindresorhus/is'; +import { logger } from '../../../logger'; import { cache } from '../../../util/cache/package/decorator'; import { regEx } from '../../../util/regex'; import { addSecretForSanitizing } from '../../../util/sanitize'; @@ -11,6 +12,7 @@ import { GithubTagsDatasource } from '../github-tags'; import { GitlabTagsDatasource } from '../gitlab-tags'; import type { DigestConfig, GetReleasesConfig, ReleaseResult } from '../types'; import { BaseGoDatasource } from './base'; +import { parseGoproxy } from './goproxy-parser'; import { GoDirectDatasource } from './releases-direct'; import { GoProxyDatasource } from './releases-goproxy'; @@ -29,6 +31,13 @@ export class GoDatasource extends Datasource { override readonly customRegistrySupport = false; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'If the release timestamp is not returned from the respective datasoure used to fetch the releases, then Renovate uses the `Time` field in the results instead.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined from the `packageName` and `registryUrl`.'; + readonly goproxy = new GoProxyDatasource(); readonly direct = new GoDirectDatasource(); @@ -49,7 +58,7 @@ export class GoDatasource extends Datasource { * go.getDigest * * This datasource resolves a go module URL into its source repository - * and then fetches the digest it if it is on GitHub. + * and then fetches the digest if it is on GitHub. * * This function will: * - Determine the source URL for the module @@ -63,6 +72,13 @@ export class GoDatasource extends Datasource { { packageName }: DigestConfig, value?: string | null, ): Promise { + if (parseGoproxy().some(({ url }) => url === 'off')) { + logger.debug( + `Skip digest fetch for ${packageName} with GOPROXY containing "off"`, + ); + return null; + } + const source = await BaseGoDatasource.getDatasource(packageName); if (!source) { return null; diff --git a/lib/modules/datasource/go/readme.md b/lib/modules/datasource/go/readme.md index 1546b270d1796c..df5dea4828834e 100644 --- a/lib/modules/datasource/go/readme.md +++ b/lib/modules/datasource/go/readme.md @@ -1,5 +1,26 @@ -This datasource will default to using the `GOPROXY` settings `https://proxy.golang.org,direct` if there is no value defined in environment variables. +The best way to lookup Go Modules is using Go proxies. -To override this default and use a different proxy, simply configure `GOPROXY` to an alternative setting in env. +## GOPROXY settings + +This datasource will use default `GOPROXY` settings of `https://proxy.golang.org,direct` if the environment variable is unset. + +To override this default and use a different proxy in self-hosted environments, configure `GOPROXY` to an alternative setting in env. To override this default and stop using any proxy at all, set `GOPROXY` to the value `direct`. + +## Pseudo versions + +Go proxies return an empty list of versions when queried (`@v/list`) for a package which uses pseudo versions, but return the latest pseudo-version when queried for `@latest`. + +If the `@latest` endpoint returns a pseudo-version, and the release list is empty, then this datasource will return the latest pseudo-version as the only release/version for the package. + +## Checking for new major releases + +When a Go proxy is queried for `@v/list` it returns only versions for v0 or v1 of a package. +Therefore Renovate will also query `@v2/list` just in case there also exists a v2 of the package. +Similarly, if the dependency is already on a higher version such as `v5`, Renovate will check in case higher major versions exist. +You do not need to be worried about any 404 responses which result from such checks - they are the only way for Renovate to know if newer major releases exist. + +## Fallback to direct lookups + +If no result is found from Go proxy lookups then Renovate will fall back to direct lookups. diff --git a/lib/modules/datasource/go/releases-goproxy.spec.ts b/lib/modules/datasource/go/releases-goproxy.spec.ts index 708c8e39ab41b2..22f6c8d1596723 100644 --- a/lib/modules/datasource/go/releases-goproxy.spec.ts +++ b/lib/modules/datasource/go/releases-goproxy.spec.ts @@ -59,223 +59,6 @@ describe('modules/datasource/go/releases-goproxy', () => { }); }); - describe('parseGoproxy', () => { - it('parses single url', () => { - const result = datasource.parseGoproxy('foo'); - expect(result).toMatchObject([{ url: 'foo' }]); - }); - - it('parses multiple urls', () => { - const result = datasource.parseGoproxy('foo,bar|baz,qux'); - expect(result).toMatchObject([ - { url: 'foo', fallback: ',' }, - { url: 'bar', fallback: '|' }, - { url: 'baz', fallback: ',' }, - { url: 'qux' }, - ]); - }); - - it('ignores everything starting from "direct" and "off" keywords', () => { - expect(datasource.parseGoproxy(undefined)).toBeEmpty(); - expect(datasource.parseGoproxy(undefined)).toBeEmpty(); - expect(datasource.parseGoproxy('')).toBeEmpty(); - expect(datasource.parseGoproxy('off')).toMatchObject([ - { url: 'off', fallback: '|' }, - ]); - expect(datasource.parseGoproxy('direct')).toMatchObject([ - { url: 'direct', fallback: '|' }, - ]); - expect(datasource.parseGoproxy('foo,off|direct,qux')).toMatchObject([ - { url: 'foo', fallback: ',' }, - { url: 'off', fallback: '|' }, - { url: 'direct', fallback: ',' }, - { url: 'qux', fallback: '|' }, - ]); - }); - }); - - describe('GoProxyDatasource.parseNoproxy', () => { - it('produces regex', () => { - expect(GoProxyDatasource.parseNoproxy(undefined)).toBeNull(); - expect(GoProxyDatasource.parseNoproxy(null)).toBeNull(); - expect(GoProxyDatasource.parseNoproxy('')).toBeNull(); - expect(GoProxyDatasource.parseNoproxy('/')).toBeNull(); - expect(GoProxyDatasource.parseNoproxy('*')?.source).toBe( - '^(?:[^\\/]*)(?:\\/.*)?$', - ); - expect(GoProxyDatasource.parseNoproxy('?')?.source).toBe( - '^(?:[^\\/])(?:\\/.*)?$', - ); - expect(GoProxyDatasource.parseNoproxy('foo')?.source).toBe( - '^(?:foo)(?:\\/.*)?$', - ); - expect(GoProxyDatasource.parseNoproxy('\\f\\o\\o')?.source).toBe( - '^(?:foo)(?:\\/.*)?$', - ); - expect(GoProxyDatasource.parseNoproxy('foo,bar')?.source).toBe( - '^(?:foo|bar)(?:\\/.*)?$', - ); - expect(GoProxyDatasource.parseNoproxy('[abc]')?.source).toBe( - '^(?:[abc])(?:\\/.*)?$', - ); - expect(GoProxyDatasource.parseNoproxy('[a-c]')?.source).toBe( - '^(?:[a-c])(?:\\/.*)?$', - ); - expect(GoProxyDatasource.parseNoproxy('[\\a-\\c]')?.source).toBe( - '^(?:[a-c])(?:\\/.*)?$', - ); - expect(GoProxyDatasource.parseNoproxy('a.b.c')?.source).toBe( - '^(?:a\\.b\\.c)(?:\\/.*)?$', - ); - expect(GoProxyDatasource.parseNoproxy('trailing/')?.source).toBe( - '^(?:trailing)(?:\\/.*)?$', - ); - }); - - it('matches on real package prefixes', () => { - expect( - GoProxyDatasource.parseNoproxy('ex.co')?.test('ex.co/foo'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('ex.co/')?.test('ex.co/foo'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('ex.co/foo/bar')?.test('ex.co/foo/bar'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('ex.co/foo/bar')?.test('ex.co/foo/bar'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('*/foo/*')?.test('example.com/foo/bar'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('ex.co/foo/*')?.test('ex.co/foo/bar'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('ex.co/foo/*')?.test('ex.co/foo/baz'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('ex.co')?.test('ex.co/foo/v2'), - ).toBeTrue(); - - expect( - GoProxyDatasource.parseNoproxy('ex.co/foo/bar')?.test('ex.co/foo/bar'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('*/foo/*')?.test('example.com/foo/bar'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('ex.co/foo/*')?.test('ex.co/foo/bar'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('ex.co/foo/*')?.test('ex.co/foo/baz'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('ex.co/foo/bar,ex.co/foo/baz')?.test( - 'ex.co/foo/bar', - ), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('ex.co/foo/bar,ex.co/foo/baz')?.test( - 'ex.co/foo/baz', - ), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('ex.co/foo/bar,ex.co/foo/baz')?.test( - 'ex.co/foo/qux', - ), - ).toBeFalse(); - - expect( - GoProxyDatasource.parseNoproxy('ex')?.test('ex.co/foo'), - ).toBeFalse(); - - expect(GoProxyDatasource.parseNoproxy('aba')?.test('x/aba')).toBeFalse(); - expect(GoProxyDatasource.parseNoproxy('x/b')?.test('x/aba')).toBeFalse(); - expect(GoProxyDatasource.parseNoproxy('x/ab')?.test('x/aba')).toBeFalse(); - expect( - GoProxyDatasource.parseNoproxy('x/ab[a-b]')?.test('x/aba'), - ).toBeTrue(); - }); - - it('matches on wildcards', () => { - expect( - GoProxyDatasource.parseNoproxy('/*/')?.test('ex.co/foo'), - ).toBeFalse(); - expect( - GoProxyDatasource.parseNoproxy('*/foo')?.test('ex.co/foo'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('*/fo')?.test('ex.co/foo'), - ).toBeFalse(); - expect( - GoProxyDatasource.parseNoproxy('*/fo?')?.test('ex.co/foo'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('*/fo*')?.test('ex.co/foo'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('*fo*')?.test('ex.co/foo'), - ).toBeFalse(); - - expect( - GoProxyDatasource.parseNoproxy('*.co')?.test('ex.co/foo'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('ex*')?.test('ex.co/foo'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('*/foo')?.test('ex.co/foo/v2'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('*/foo/')?.test('ex.co/foo/v2'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('*/foo/*')?.test('ex.co/foo/v2'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('*/foo/*/')?.test('ex.co/foo/v2'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('*/v2')?.test('ex.co/foo/v2'), - ).toBeFalse(); - expect( - GoProxyDatasource.parseNoproxy('*/*/v2')?.test('ex.co/foo/v2'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('*/*/*')?.test('ex.co/foo/v2'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('*/*/*/')?.test('ex.co/foo/v2'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('*/*/*')?.test('ex.co/foo'), - ).toBeFalse(); - expect( - GoProxyDatasource.parseNoproxy('*/*/*/')?.test('ex.co/foo'), - ).toBeFalse(); - - expect( - GoProxyDatasource.parseNoproxy('*/*/*,,')?.test('ex.co/repo'), - ).toBeFalse(); - expect( - GoProxyDatasource.parseNoproxy('*/*/*,,*/repo')?.test('ex.co/repo'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy(',,*/repo')?.test('ex.co/repo'), - ).toBeTrue(); - }); - - it('matches on character ranges', () => { - expect( - GoProxyDatasource.parseNoproxy('x/ab[a-b]')?.test('x/aba'), - ).toBeTrue(); - expect( - GoProxyDatasource.parseNoproxy('x/ab[a-b]')?.test('x/abc'), - ).toBeFalse(); - }); - }); - describe('getReleases', () => { const baseUrl = 'https://proxy.golang.org'; @@ -767,7 +550,7 @@ describe('modules/datasource/go/releases-goproxy', () => { expect(res).toBeNull(); }); - it('returns latest even if package has no releases', async () => { + it('uses latest if package has no releases', async () => { process.env.GOPROXY = baseUrl; httpMock @@ -784,7 +567,13 @@ describe('modules/datasource/go/releases-goproxy', () => { }); expect(res).toEqual({ - releases: [], + releases: [ + { + newDigest: '921286631fa9', + releaseTimestamp: '2023-09-05T20:02:55Z', + version: 'v0.0.0-20230905200255-921286631fa9', + }, + ], sourceUrl: 'https://github.com/google/btree', tags: { latest: 'v0.0.0-20230905200255-921286631fa9' }, }); diff --git a/lib/modules/datasource/go/releases-goproxy.ts b/lib/modules/datasource/go/releases-goproxy.ts index fc98b95b23a714..432bf48ebffcc8 100644 --- a/lib/modules/datasource/go/releases-goproxy.ts +++ b/lib/modules/datasource/go/releases-goproxy.ts @@ -1,6 +1,5 @@ import is from '@sindresorhus/is'; import { DateTime } from 'luxon'; -import moo from 'moo'; import { logger } from '../../../logger'; import { cache } from '../../../util/cache/package/decorator'; import { filterMap } from '../../../util/filter-map'; @@ -12,10 +11,9 @@ import { Datasource } from '../datasource'; import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; import { BaseGoDatasource } from './base'; import { getSourceUrl } from './common'; +import { parseGoproxy, parseNoproxy } from './goproxy-parser'; import { GoDirectDatasource } from './releases-direct'; -import type { GoproxyItem, VersionInfo } from './types'; - -const parsedGoproxy: Record = {}; +import type { VersionInfo } from './types'; const modRegex = regEx(/^(?.*?)(?:[./]v(?\d+))?$/); @@ -26,6 +24,24 @@ const pseudoVersionRegex = regEx( /v\d+\.\d+\.\d+-(?:\w+\.)?(?:0\.)?(?\d{14})-(?[a-f0-9]{12})/i, ); +export function pseudoVersionToRelease(pseudoVersion: string): Release | null { + const match = pseudoVersion.match(pseudoVersionRegex)?.groups; + if (!match) { + return null; + } + + const { digest: newDigest, timestamp } = match; + const releaseTimestamp = DateTime.fromFormat(timestamp, 'yyyyMMddHHmmss', { + zone: 'UTC', + }).toISO({ suppressMilliseconds: true }); + + return { + version: pseudoVersion, + newDigest, + releaseTimestamp, + }; +} + export class GoProxyDatasource extends Datasource { static readonly id = 'go-proxy'; @@ -46,8 +62,8 @@ export class GoProxyDatasource extends Datasource { if (goproxy === 'direct') { return this.direct.getReleases(config); } - const proxyList = this.parseGoproxy(goproxy); - const noproxy = GoProxyDatasource.parseNoproxy(); + const proxyList = parseGoproxy(goproxy); + const noproxy = parseNoproxy(); let result: ReleaseResult | null = null; @@ -71,9 +87,6 @@ export class GoProxyDatasource extends Datasource { result = res; break; } - if (res.tags?.latest) { - result = res; - } } catch (err) { const statusCode = err?.response?.statusCode; const canFallback = @@ -103,106 +116,6 @@ export class GoProxyDatasource extends Datasource { return result; } - /** - * Parse `GOPROXY` to the sequence of url + fallback strategy tags. - * - * @example - * parseGoproxy('foo.example.com|bar.example.com,baz.example.com') - * // [ - * // { url: 'foo.example.com', fallback: '|' }, - * // { url: 'bar.example.com', fallback: ',' }, - * // { url: 'baz.example.com', fallback: '|' }, - * // ] - * - * @see https://golang.org/ref/mod#goproxy-protocol - */ - parseGoproxy(input: string | undefined = process.env.GOPROXY): GoproxyItem[] { - if (!is.string(input)) { - return []; - } - - if (parsedGoproxy[input]) { - return parsedGoproxy[input]; - } - - const result: GoproxyItem[] = input - .split(regEx(/([^,|]*(?:,|\|))/)) - .filter(Boolean) - .map((s) => s.split(/(?=,|\|)/)) // TODO: #12872 lookahead - .map(([url, separator]) => ({ - url, - fallback: separator === ',' ? ',' : '|', - })); - - parsedGoproxy[input] = result; - return result; - } - // https://golang.org/pkg/path/#Match - static lexer = moo.states({ - main: { - separator: { - match: /\s*?,\s*?/, // TODO #12870 - value: (_: string) => '|', - }, - asterisk: { - match: '*', - value: (_: string) => '[^/]*', - }, - qmark: { - match: '?', - value: (_: string) => '[^/]', - }, - characterRangeOpen: { - match: '[', - push: 'characterRange', - value: (_: string) => '[', - }, - trailingSlash: { - match: /\/$/, - value: (_: string) => '', - }, - char: { - match: /[^*?\\[\n]/, - value: (s: string) => s.replace(regEx('\\.', 'g'), '\\.'), - }, - escapedChar: { - match: /\\./, // TODO #12870 - value: (s: string) => s.slice(1), - }, - }, - characterRange: { - char: /[^\\\]\n]/, // TODO #12870 - escapedChar: { - match: /\\./, // TODO #12870 - value: (s: string) => s.slice(1), - }, - characterRangeEnd: { - match: ']', - pop: 1, - }, - }, - }); - - static parsedNoproxy: Record = {}; - - static parseNoproxy( - input: unknown = process.env.GONOPROXY ?? process.env.GOPRIVATE, - ): RegExp | null { - if (!is.string(input)) { - return null; - } - if (this.parsedNoproxy[input] !== undefined) { - return this.parsedNoproxy[input]; - } - this.lexer.reset(input); - const noproxyPattern = [...this.lexer].map(({ value }) => value).join(''); - const result = noproxyPattern - ? regEx(`^(?:${noproxyPattern})(?:/.*)?$`) - : null; - this.parsedNoproxy[input] = result; - return result; - } - /** * Avoid ambiguity when serving from case-insensitive file systems. * @@ -221,30 +134,12 @@ export class GoProxyDatasource extends Datasource { } const [version, releaseTimestamp] = str.trim().split(regEx(/\s+/)); - const release: Release = { version }; + const release: Release = pseudoVersionToRelease(version) ?? { version }; if (releaseTimestamp) { release.releaseTimestamp = releaseTimestamp; } - const pseudoVersionMatch = version.match(pseudoVersionRegex)?.groups; - if (pseudoVersionMatch) { - const { digest: newDigest, timestamp } = pseudoVersionMatch; - - if (newDigest) { - release.newDigest = newDigest; - } - - const pseudoVersionReleaseTimestamp = DateTime.fromFormat( - timestamp, - 'yyyyMMddHHmmss', - { zone: 'UTC' }, - ).toISO({ suppressMilliseconds: true }); - if (pseudoVersionReleaseTimestamp) { - release.releaseTimestamp = pseudoVersionReleaseTimestamp; - } - } - return release; }); } @@ -336,6 +231,12 @@ export class GoProxyDatasource extends Datasource { if (goVersioning.isGreaterThan(latestVersion, result.tags.latest)) { result.tags.latest = latestVersion; } + if (!result.releases.length) { + const releaseFromLatest = pseudoVersionToRelease(latestVersion); + if (releaseFromLatest) { + result.releases.push(releaseFromLatest); + } + } } } @@ -344,7 +245,7 @@ export class GoProxyDatasource extends Datasource { static getCacheKey({ packageName }: GetReleasesConfig): string { const goproxy = process.env.GOPROXY; - const noproxy = GoProxyDatasource.parseNoproxy(); + const noproxy = parseNoproxy(); // TODO: types (#22198) return `${packageName}@@${goproxy}@@${noproxy?.toString()}`; } diff --git a/lib/modules/datasource/golang-version/index.ts b/lib/modules/datasource/golang-version/index.ts index 79e907f3418136..65e7d9b87b37c3 100644 --- a/lib/modules/datasource/golang-version/index.ts +++ b/lib/modules/datasource/golang-version/index.ts @@ -32,6 +32,13 @@ export class GolangVersionDatasource extends Datasource { override readonly defaultVersioning = semverVersioningId; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `Date` field in the results.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'We use the URL: https://github.com/golang/go.'; + @cache({ namespace: `datasource-${GolangVersionDatasource.id}`, key: 'all' }) async getReleases({ registryUrl, diff --git a/lib/modules/datasource/gradle-version/index.ts b/lib/modules/datasource/gradle-version/index.ts index 069b51f7190a77..bb6368e19803f1 100644 --- a/lib/modules/datasource/gradle-version/index.ts +++ b/lib/modules/datasource/gradle-version/index.ts @@ -20,6 +20,13 @@ export class GradleVersionDatasource extends Datasource { override readonly registryStrategy = 'merge'; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `buildTime` field in the results.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'We use the URL: https://github.com/gradle/gradle.'; + private static readonly buildTimeRegex = regEx( '^(\\d\\d\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)(\\+\\d\\d\\d\\d)$', ); diff --git a/lib/modules/datasource/helm/index.ts b/lib/modules/datasource/helm/index.ts index ada0c21779c1b8..e5ccc32b5113d1 100644 --- a/lib/modules/datasource/helm/index.ts +++ b/lib/modules/datasource/helm/index.ts @@ -25,6 +25,13 @@ export class HelmDatasource extends Datasource { override readonly defaultVersioning = helmVersioning.id; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timstamp is determined from the `created` field in the results.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined from the `home` field or the `sources` field in the results.'; + @cache({ namespace: `datasource-${HelmDatasource.id}`, key: (helmRepository: string) => helmRepository, diff --git a/lib/modules/datasource/hermit/index.ts b/lib/modules/datasource/hermit/index.ts index 4db90e81b975ec..167c1544e64a2a 100644 --- a/lib/modules/datasource/hermit/index.ts +++ b/lib/modules/datasource/hermit/index.ts @@ -30,6 +30,10 @@ export class HermitDatasource extends Datasource { 'https://github.com/cashapp/hermit-packages', ]; + override readonly sourceUrlSupport = 'release'; + override readonly sourceUrlNote = + 'The source URL is determined from the `Repository` field in the results.'; + pathRegex: RegExp; constructor() { diff --git a/lib/modules/datasource/hex/index.ts b/lib/modules/datasource/hex/index.ts index e6ee7d927cc364..c21b87785402ee 100644 --- a/lib/modules/datasource/hex/index.ts +++ b/lib/modules/datasource/hex/index.ts @@ -18,6 +18,13 @@ export class HexDatasource extends Datasource { override readonly defaultVersioning = hexVersioning.id; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined the `inserted_at` field in the results.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined from the `Github` field in the results.'; + @cache({ namespace: `datasource-${HexDatasource.id}`, key: ({ packageName }: GetReleasesConfig) => packageName, diff --git a/lib/modules/datasource/hexpm-bob/index.ts b/lib/modules/datasource/hexpm-bob/index.ts index 5d1f0a89ee1086..c841f26e9a38d4 100644 --- a/lib/modules/datasource/hexpm-bob/index.ts +++ b/lib/modules/datasource/hexpm-bob/index.ts @@ -24,6 +24,13 @@ export class HexpmBobDatasource extends Datasource { override readonly defaultVersioning = semverId; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `buildDate` field in the results.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'We use the URL https://github.com/elixir-lang/elixir.git for the `elixir` package and the https://github.com/erlang/otp.git URL for the `erlang` package.'; + @cache({ namespace: `datasource-${datasource}`, key: ({ registryUrl, packageName }: GetReleasesConfig) => diff --git a/lib/modules/datasource/index.ts b/lib/modules/datasource/index.ts index 743552e57ea2b8..a8a7d1c1c6502a 100644 --- a/lib/modules/datasource/index.ts +++ b/lib/modules/datasource/index.ts @@ -398,15 +398,18 @@ function getDigestConfig( datasource: DatasourceApi, config: GetDigestInputConfig, ): DigestConfig { - const { currentValue, currentDigest } = config; + const { lookupName, currentValue, currentDigest } = config; const packageName = config.replacementName ?? config.packageName; - const [registryUrl] = resolveRegistryUrls( - datasource, - config.defaultRegistryUrls, - config.registryUrls, - config.additionalRegistryUrls, - ); - return { packageName, registryUrl, currentValue, currentDigest }; + // Prefer registryUrl from getReleases() lookup if it has been passed + const registryUrl = + config.registryUrl ?? + resolveRegistryUrls( + datasource, + config.defaultRegistryUrls, + config.registryUrls, + config.additionalRegistryUrls, + )[0]; + return { lookupName, packageName, registryUrl, currentValue, currentDigest }; } export function getDigest( diff --git a/lib/modules/datasource/jenkins-plugins/index.ts b/lib/modules/datasource/jenkins-plugins/index.ts index a19ead3bb18e33..28cbca46d5831d 100644 --- a/lib/modules/datasource/jenkins-plugins/index.ts +++ b/lib/modules/datasource/jenkins-plugins/index.ts @@ -25,6 +25,13 @@ export class JenkinsPluginsDatasource extends Datasource { private static readonly packageInfoPath = 'current/update-center.actual.json'; private static readonly packageVersionsPath = 'current/plugin-versions.json'; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The releaseTimestamp is determined from the `releaseTimestamp` or `buildDate` field in the results.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined from the `scm` field in the results.'; + async getReleases({ packageName, registryUrl, diff --git a/lib/modules/datasource/maven/__snapshots__/index.spec.ts.snap b/lib/modules/datasource/maven/__snapshots__/index.spec.ts.snap index bdc86e88e02ff1..d6ca5371d2e484 100644 --- a/lib/modules/datasource/maven/__snapshots__/index.spec.ts.snap +++ b/lib/modules/datasource/maven/__snapshots__/index.spec.ts.snap @@ -6,6 +6,7 @@ exports[`modules/datasource/maven/index falls back to next registry url 1`] = ` "group": "org.example", "homepage": "https://package.example.org/about", "name": "package", + "packageScope": "org.example", "registryUrl": "https://repo.maven.apache.org/maven2", "releases": [ { @@ -53,6 +54,7 @@ exports[`modules/datasource/maven/index removes authentication header after redi "group": "org.example", "homepage": "https://package.example.org/about", "name": "package", + "packageScope": "org.example", "registryUrl": "https://frontend_for_private_s3_repository/maven2", "releases": [ { @@ -89,6 +91,7 @@ exports[`modules/datasource/maven/index returns releases 1`] = ` "group": "org.example", "homepage": "https://package.example.org/about", "name": "package", + "packageScope": "org.example", "registryUrl": "https://repo.maven.apache.org/maven2", "releases": [ { @@ -116,6 +119,7 @@ exports[`modules/datasource/maven/index returns releases from custom repository "group": "org.example", "homepage": "https://package.example.org/about", "name": "package", + "packageScope": "org.example", "registryUrl": "https://custom.registry.renovatebot.com", "releases": [ { @@ -143,6 +147,7 @@ exports[`modules/datasource/maven/index skips registry with invalid XML 1`] = ` "group": "org.example", "homepage": "https://package.example.org/about", "name": "package", + "packageScope": "org.example", "registryUrl": "https://repo.maven.apache.org/maven2", "releases": [ { @@ -170,6 +175,7 @@ exports[`modules/datasource/maven/index skips registry with invalid metadata str "group": "org.example", "homepage": "https://package.example.org/about", "name": "package", + "packageScope": "org.example", "registryUrl": "https://repo.maven.apache.org/maven2", "releases": [ { diff --git a/lib/modules/datasource/maven/index.spec.ts b/lib/modules/datasource/maven/index.spec.ts index f0d931a336450e..d51f6f1f025a1d 100644 --- a/lib/modules/datasource/maven/index.spec.ts +++ b/lib/modules/datasource/maven/index.spec.ts @@ -240,6 +240,7 @@ describe('modules/datasource/maven/index', () => { group: 'org.example', homepage: 'https://package.example.org/about', name: 'package', + packageScope: 'org.example', registryUrl: 'https://repo.maven.apache.org/maven2', releases: [ { @@ -268,6 +269,7 @@ describe('modules/datasource/maven/index', () => { group: 'org.example', homepage: 'https://package.example.org/about', name: 'package', + packageScope: 'org.example', registryUrl: 'https://repo.maven.apache.org/maven2', releases: [ { version: '1.0.0', releaseTimestamp: '2021-02-22T14:43:00.000Z' }, @@ -484,6 +486,7 @@ describe('modules/datasource/maven/index', () => { group: 'org.example', homepage: 'https://package.example.org/about', name: 'package', + packageScope: 'org.example', registryUrl: 'artifactregistry://maven.pkg.dev/some-project/some-repository', releases: [ @@ -535,6 +538,7 @@ describe('modules/datasource/maven/index', () => { group: 'org.example', homepage: 'https://package.example.org/about', name: 'package', + packageScope: 'org.example', registryUrl: 'artifactregistry://maven.pkg.dev/some-project/some-repository', releases: [ diff --git a/lib/modules/datasource/maven/index.ts b/lib/modules/datasource/maven/index.ts index 3b2c752d8714ce..3232823257c48e 100644 --- a/lib/modules/datasource/maven/index.ts +++ b/lib/modules/datasource/maven/index.ts @@ -69,6 +69,13 @@ export class MavenDatasource extends Datasource { override readonly registryStrategy: RegistryStrategy = 'merge'; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `Last-Modified` header or the `lastModified` field in the results.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined from the `scm` tags in the results.'; + constructor(id = MavenDatasource.id) { super(id); } @@ -114,6 +121,10 @@ export class MavenDatasource extends Datasource { dependency: MavenDependency, repoUrl: string, ): Promise { + if (!repoUrl.startsWith(MAVEN_REPO)) { + return inputReleaseMap; + } + const cacheNs = 'datasource-maven:index-html-releases'; const cacheKey = `${repoUrl}${dependency.dependencyUrl}`; let workingReleaseMap = await packageCache.get( @@ -124,27 +135,21 @@ export class MavenDatasource extends Datasource { workingReleaseMap = {}; let retryEarlier = false; try { - if (repoUrl.startsWith(MAVEN_REPO)) { - const indexUrl = getMavenUrl(dependency, repoUrl, 'index.html'); - const res = await downloadHttpProtocol(this.http, indexUrl); - const { body = '' } = res; - for (const line of body.split(newlineRegex)) { - const match = line.trim().match(mavenCentralHtmlVersionRegex); - if (match) { - const { version, releaseTimestamp: timestamp } = - match?.groups ?? /* istanbul ignore next: hard to test */ {}; - if (version && timestamp) { - const date = DateTime.fromFormat( - timestamp, - 'yyyy-MM-dd HH:mm', - { - zone: 'UTC', - }, - ); - if (date.isValid) { - const releaseTimestamp = date.toISO(); - workingReleaseMap[version] = { version, releaseTimestamp }; - } + const indexUrl = getMavenUrl(dependency, repoUrl, 'index.html'); + const res = await downloadHttpProtocol(this.http, indexUrl); + const { body = '' } = res; + for (const line of body.split(newlineRegex)) { + const match = line.trim().match(mavenCentralHtmlVersionRegex); + if (match) { + const { version, releaseTimestamp: timestamp } = + match?.groups ?? /* istanbul ignore next: hard to test */ {}; + if (version && timestamp) { + const date = DateTime.fromFormat(timestamp, 'yyyy-MM-dd HH:mm', { + zone: 'UTC', + }); + if (date.isValid) { + const releaseTimestamp = date.toISO(); + workingReleaseMap[version] = { version, releaseTimestamp }; } } } diff --git a/lib/modules/datasource/maven/util.ts b/lib/modules/datasource/maven/util.ts index 072699cc2cb2ab..24b92cc02dfbef 100644 --- a/lib/modules/datasource/maven/util.ts +++ b/lib/modules/datasource/maven/util.ts @@ -6,11 +6,7 @@ import { HOST_DISABLED } from '../../../constants/error-messages'; import { logger } from '../../../logger'; import { ExternalHostError } from '../../../types/errors/external-host-error'; import type { Http } from '../../../util/http'; -import type { - HttpOptions, - HttpRequestOptions, - HttpResponse, -} from '../../../util/http/types'; +import type { HttpOptions, HttpResponse } from '../../../util/http/types'; import { regEx } from '../../../util/regex'; import { getS3Client, parseS3Url } from '../../../util/s3'; import { streamToString } from '../../../util/streams'; @@ -69,7 +65,7 @@ function isUnsupportedHostError(err: { name: string }): boolean { export async function downloadHttpProtocol( http: Http, pkgUrl: URL | string, - opts: HttpOptions & HttpRequestOptions = {}, + opts: HttpOptions = {}, ): Promise> { let raw: HttpResponse; try { @@ -449,6 +445,11 @@ export async function getDependencyInfo( } } + const groupId = pomContent.valueWithPath('groupId'); + if (groupId) { + result.packageScope = groupId; + } + const parent = pomContent.childNamed('parent'); if (recursionLimit > 0 && parent && (!result.sourceUrl || !result.homepage)) { // if we found a parent and are missing some information diff --git a/lib/modules/datasource/metadata-manual.ts b/lib/modules/datasource/metadata-manual.ts index eb77f8583340c9..7b59ad571eea0a 100644 --- a/lib/modules/datasource/metadata-manual.ts +++ b/lib/modules/datasource/metadata-manual.ts @@ -17,17 +17,11 @@ export const manualChangelogUrls: Record> = { 'https://github.com/angular/angular/blob/master/packages/zone.js/CHANGELOG.md', }, pypi: { - alembic: 'https://alembic.sqlalchemy.org/en/latest/changelog.html', beautifulsoup4: 'https://bazaar.launchpad.net/~leonardr/beautifulsoup/bs4/view/head:/CHANGELOG', - django: 'https://github.com/django/django/tree/master/docs/releases', - djangorestframework: - 'https://www.django-rest-framework.org/community/release-notes/', flake8: 'https://flake8.pycqa.org/en/latest/release-notes/index.html', 'django-storages': 'https://github.com/jschneier/django-storages/blob/master/CHANGELOG.rst', - hypothesis: - 'https://github.com/HypothesisWorks/hypothesis/blob/master/hypothesis-python/docs/changes.rst', lxml: 'https://git.launchpad.net/lxml/plain/CHANGES.txt', mypy: 'https://mypy-lang.blogspot.com/', phonenumbers: @@ -38,12 +32,9 @@ export const manualChangelogUrls: Record> = { 'https://github.com/flyingcircusio/pycountry/blob/master/HISTORY.txt', 'django-debug-toolbar': 'https://django-debug-toolbar.readthedocs.io/en/latest/changes.html', - 'firebase-admin': - 'https://firebase.google.com/support/release-notes/admin/python', requests: 'https://github.com/psf/requests/blob/master/HISTORY.md', sqlalchemy: 'https://docs.sqlalchemy.org/en/latest/changelog/', uwsgi: 'https://uwsgi-docs.readthedocs.io/en/latest/#release-notes', - wagtail: 'https://github.com/wagtail/wagtail/tree/master/docs/releases', }, docker: { 'gitlab/gitlab-ce': diff --git a/lib/modules/datasource/metadata.spec.ts b/lib/modules/datasource/metadata.spec.ts index 73fc900561cdb3..0b49c803ce8db8 100644 --- a/lib/modules/datasource/metadata.spec.ts +++ b/lib/modules/datasource/metadata.spec.ts @@ -27,12 +27,12 @@ describe('modules/datasource/metadata', () => { }; const datasource = PypiDatasource.id; - const packageName = 'django'; + const packageName = 'pycountry'; addMetaData(dep, datasource, packageName); expect(dep).toMatchSnapshot({ changelogUrl: - 'https://github.com/django/django/tree/master/docs/releases', + 'https://github.com/flyingcircusio/pycountry/blob/master/HISTORY.txt', }); }); @@ -512,13 +512,13 @@ describe('modules/datasource/metadata', () => { const dep = partial({}); const datasource = PypiDatasource.id; - const packageName = 'django'; + const packageName = 'pycountry'; addMetaData(dep, datasource, packageName); expect(dep).toEqual({ changelogUrl: - 'https://github.com/django/django/tree/master/docs/releases', - sourceUrl: 'https://github.com/django/django', + 'https://github.com/flyingcircusio/pycountry/blob/master/HISTORY.txt', + sourceUrl: 'https://github.com/flyingcircusio/pycountry', }); }); diff --git a/lib/modules/datasource/metadata.ts b/lib/modules/datasource/metadata.ts index cbe96e040eb5ad..c0b1cd6a5de6f3 100644 --- a/lib/modules/datasource/metadata.ts +++ b/lib/modules/datasource/metadata.ts @@ -5,7 +5,7 @@ import { detectPlatform } from '../../util/common'; import { parseGitUrl } from '../../util/git/url'; import * as hostRules from '../../util/host-rules'; import { regEx } from '../../util/regex'; -import { parseUrl, trimTrailingSlash, validateUrl } from '../../util/url'; +import { isHttpUrl, parseUrl, trimTrailingSlash } from '../../util/url'; import { manualChangelogUrls, manualSourceUrls } from './metadata-manual'; import type { ReleaseResult } from './types'; @@ -195,7 +195,7 @@ export function addMetaData( ]; for (const urlKey of urlKeys) { const urlVal = dep[urlKey]; - if (is.string(urlVal) && validateUrl(urlVal.trim())) { + if (is.string(urlVal) && isHttpUrl(urlVal.trim())) { dep[urlKey] = urlVal.trim() as never; } else { delete dep[urlKey]; diff --git a/lib/modules/datasource/node-version/index.ts b/lib/modules/datasource/node-version/index.ts index f93b88bfa8716d..c37c707c32cabf 100644 --- a/lib/modules/datasource/node-version/index.ts +++ b/lib/modules/datasource/node-version/index.ts @@ -13,14 +13,19 @@ export class NodeVersionDatasource extends Datasource { super(datasource); } - override readonly customRegistrySupport = false; - override readonly defaultRegistryUrls = [defaultRegistryUrl]; override readonly defaultVersioning = versioning; override readonly caching = true; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `date` field.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'We use the URL: https://github.com/nodejs/node'; + @cache({ namespace: `datasource-${datasource}`, // TODO: types (#22198) diff --git a/lib/modules/datasource/npm/__snapshots__/index.spec.ts.snap b/lib/modules/datasource/npm/__snapshots__/index.spec.ts.snap index 02cf5b7c7e009e..643d0d75ba63df 100644 --- a/lib/modules/datasource/npm/__snapshots__/index.spec.ts.snap +++ b/lib/modules/datasource/npm/__snapshots__/index.spec.ts.snap @@ -221,6 +221,7 @@ Marking the latest version of an npm package as deprecated results in the entire "dependencies": undefined, "devDependencies": undefined, "gitRef": undefined, + "isDeprecated": true, "releaseTimestamp": "2018-05-06T05:21:53.000Z", "version": "0.0.1", }, diff --git a/lib/modules/datasource/npm/get.spec.ts b/lib/modules/datasource/npm/get.spec.ts index b101911166ee7b..b8a3809bc4bc28 100644 --- a/lib/modules/datasource/npm/get.spec.ts +++ b/lib/modules/datasource/npm/get.spec.ts @@ -4,7 +4,7 @@ import { ExternalHostError } from '../../../types/errors/external-host-error'; import * as _packageCache from '../../../util/cache/package'; import * as hostRules from '../../../util/host-rules'; import { Http } from '../../../util/http'; -import { getDependency } from './get'; +import { CACHE_REVISION, getDependency } from './get'; import { resolveRegistryUrl, setNpmrc } from './npmrc'; jest.mock('../../../util/cache/package'); @@ -245,6 +245,91 @@ describe('modules/datasource/npm/get', () => { expect(await getDependency(http, registryUrl, 'npm-error-402')).toBeNull(); }); + it('throw ExternalHostError when error happens on registry.npmjs.org', async () => { + httpMock + .scope('https://registry.npmjs.org') + .get('/npm-parse-error') + .reply(200, 'not-a-json'); + const registryUrl = resolveRegistryUrl('npm-parse-error'); + await expect( + getDependency(http, registryUrl, 'npm-parse-error'), + ).rejects.toThrow(ExternalHostError); + }); + + it('redact body for ExternalHostError when error happens on registry.npmjs.org', async () => { + httpMock + .scope('https://registry.npmjs.org') + .get('/npm-parse-error') + .reply(200, 'not-a-json'); + const registryUrl = resolveRegistryUrl('npm-parse-error'); + let thrownError; + try { + await getDependency(http, registryUrl, 'npm-parse-error'); + } catch (error) { + thrownError = error; + } + expect(thrownError.err.name).toBe('ParseError'); + expect(thrownError.err.body).toBe('err.body deleted by Renovate'); + }); + + it('do not throw ExternalHostError when error happens on custom host', async () => { + setNpmrc('registry=https://test.org'); + httpMock + .scope('https://test.org') + .get('/npm-parse-error') + .reply(200, 'not-a-json'); + const registryUrl = resolveRegistryUrl('npm-parse-error'); + expect( + await getDependency(http, registryUrl, 'npm-parse-error'), + ).toBeNull(); + }); + + it('do not throw ExternalHostError when error happens on registry.npmjs.org when hostRules disables abortOnError', async () => { + hostRules.add({ + matchHost: 'https://registry.npmjs.org', + abortOnError: false, + }); + httpMock + .scope('https://registry.npmjs.org') + .get('/npm-parse-error') + .reply(200, 'not-a-json'); + const registryUrl = resolveRegistryUrl('npm-parse-error'); + expect( + await getDependency(http, registryUrl, 'npm-parse-error'), + ).toBeNull(); + }); + + it('do not throw ExternalHostError when error happens on registry.npmjs.org when hostRules without protocol disables abortOnError', async () => { + hostRules.add({ + matchHost: 'registry.npmjs.org', + abortOnError: false, + }); + httpMock + .scope('https://registry.npmjs.org') + .get('/npm-parse-error') + .reply(200, 'not-a-json'); + const registryUrl = resolveRegistryUrl('npm-parse-error'); + expect( + await getDependency(http, registryUrl, 'npm-parse-error'), + ).toBeNull(); + }); + + it('throw ExternalHostError when error happens on custom host when hostRules enables abortOnError', async () => { + setNpmrc('registry=https://test.org'); + hostRules.add({ + matchHost: 'https://test.org', + abortOnError: true, + }); + httpMock + .scope('https://test.org') + .get('/npm-parse-error') + .reply(200, 'not-a-json'); + const registryUrl = resolveRegistryUrl('npm-parse-error'); + await expect( + getDependency(http, registryUrl, 'npm-parse-error'), + ).rejects.toThrow(ExternalHostError); + }); + it('massages non-compliant repository urls', async () => { setNpmrc('registry=https://test.org\n_authToken=XXX'); @@ -473,16 +558,31 @@ describe('modules/datasource/npm/get', () => { `); }); - it('returns cached legacy', async () => { - packageCache.get.mockResolvedValueOnce({ some: 'result' }); - const dep = await getDependency(http, 'https://some.url', 'some-package'); - expect(dep).toMatchObject({ some: 'result' }); + it('discards cache with no revision', async () => { + setNpmrc('registry=https://test.org\n_authToken=XXX'); + + packageCache.get.mockResolvedValueOnce({ + some: 'result', + cacheData: { softExpireAt: '2099' }, + }); + + httpMock + .scope('https://test.org') + .get('/@neutrinojs%2Freact') + .reply(200, { + name: '@neutrinojs/react', + versions: { '1.0.0': {} }, + }); + const registryUrl = resolveRegistryUrl('@neutrinojs/react'); + const dep = await getDependency(http, registryUrl, '@neutrinojs/react'); + + expect(dep?.releases).toHaveLength(1); }); it('returns unexpired cache', async () => { packageCache.get.mockResolvedValueOnce({ some: 'result', - cacheData: { softExpireAt: '2099' }, + cacheData: { revision: CACHE_REVISION, softExpireAt: '2099' }, }); const dep = await getDependency(http, 'https://some.url', 'some-package'); expect(dep).toMatchObject({ some: 'result' }); @@ -492,6 +592,7 @@ describe('modules/datasource/npm/get', () => { packageCache.get.mockResolvedValueOnce({ some: 'result', cacheData: { + revision: CACHE_REVISION, softExpireAt: '2020', etag: 'some-etag', }, @@ -508,6 +609,7 @@ describe('modules/datasource/npm/get', () => { packageCache.get.mockResolvedValueOnce({ some: 'result', cacheData: { + revision: CACHE_REVISION, softExpireAt: '2020', etag: 'some-etag', }, diff --git a/lib/modules/datasource/npm/get.ts b/lib/modules/datasource/npm/get.ts index 6018d41efbac4b..cc8024ebdd0e80 100644 --- a/lib/modules/datasource/npm/get.ts +++ b/lib/modules/datasource/npm/get.ts @@ -7,13 +7,17 @@ import { HOST_DISABLED } from '../../../constants/error-messages'; import { logger } from '../../../logger'; import { ExternalHostError } from '../../../types/errors/external-host-error'; import * as packageCache from '../../../util/cache/package'; +import * as hostRules from '../../../util/host-rules'; import type { Http } from '../../../util/http'; import type { HttpOptions } from '../../../util/http/types'; import { regEx } from '../../../util/regex'; +import { HttpCacheStats } from '../../../util/stats'; import { joinUrlParts } from '../../../util/url'; import type { Release, ReleaseResult } from '../types'; import type { CachedReleaseResult, NpmResponse } from './types'; +export const CACHE_REVISION = 1; + const SHORT_REPO_REGEX = regEx( /^((?bitbucket|github|gitlab):)?(?[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)$/, ); @@ -82,20 +86,25 @@ export async function getDependency( cacheNamespace, packageUrl, ); - if (cachedResult) { - if (cachedResult.cacheData) { + if (cachedResult?.cacheData) { + if (cachedResult.cacheData.revision === CACHE_REVISION) { const softExpireAt = DateTime.fromISO( cachedResult.cacheData.softExpireAt, ); if (softExpireAt.isValid && softExpireAt > DateTime.local()) { logger.trace('Cached result is not expired - reusing'); + HttpCacheStats.incLocalHits(packageUrl); delete cachedResult.cacheData; return cachedResult; } + logger.trace('Cached result is soft expired'); + HttpCacheStats.incLocalMisses(packageUrl); } else { - logger.trace('Reusing legacy cached result'); - return cachedResult; + logger.trace( + `Package cache for npm package "${packageName}" is from an old revision - discarding`, + ); + delete cachedResult.cacheData; } } const cacheMinutes = process.env.RENOVATE_CACHE_NPM_MINUTES @@ -120,9 +129,27 @@ export async function getDependency( logger.trace({ packageName }, 'Using cached etag'); options.headers = { 'If-None-Match': cachedResult.cacheData.etag }; } + + // set abortOnError for registry.npmjs.org if no hostRule with explicit abortOnError exists + if ( + registryUrl === 'https://registry.npmjs.org' && + hostRules.find({ url: 'https://registry.npmjs.org' })?.abortOnError === + undefined + ) { + logger.trace( + { packageName, registry: 'https://registry.npmjs.org' }, + 'setting abortOnError hostRule for well known host', + ); + hostRules.add({ + matchHost: 'https://registry.npmjs.org', + abortOnError: true, + }); + } + const raw = await http.getJson(packageUrl, options); if (cachedResult?.cacheData && raw.statusCode === 304) { logger.trace(`Cached npm result for ${packageName} is revalidated`); + HttpCacheStats.incRemoteHits(packageUrl); cachedResult.cacheData.softExpireAt = softExpireAt; await packageCache.set( cacheNamespace, @@ -133,6 +160,7 @@ export async function getDependency( delete cachedResult.cacheData; return cachedResult; } + HttpCacheStats.incRemoteMisses(packageUrl); const etag = raw.headers.etag; const res = raw.body; if (!res.versions || !Object.keys(res.versions).length) { @@ -193,6 +221,9 @@ export async function getDependency( ) { release.sourceDirectory = source.sourceDirectory; } + if (dep.deprecationMessage) { + release.isDeprecated = true; + } return release; }); logger.trace({ dep }, 'dep'); @@ -202,7 +233,7 @@ export async function getDependency( regEx(/(^|,)\s*public\s*(,|$)/).test(cacheControl) ) { dep.isPrivate = false; - const cacheData = { softExpireAt, etag }; + const cacheData = { revision: CACHE_REVISION, softExpireAt, etag }; await packageCache.set( cacheNamespace, packageUrl, @@ -216,29 +247,32 @@ export async function getDependency( } return dep; } catch (err) { + const actualError = err instanceof ExternalHostError ? err.err : err; const ignoredStatusCodes = [401, 402, 403, 404]; const ignoredResponseCodes = ['ENOTFOUND']; if ( - err.message === HOST_DISABLED || - ignoredStatusCodes.includes(err.statusCode) || - ignoredResponseCodes.includes(err.code) + actualError.message === HOST_DISABLED || + ignoredStatusCodes.includes(actualError.statusCode) || + ignoredResponseCodes.includes(actualError.code) ) { return null; } - if (uri.host === 'registry.npmjs.org') { + + if (err instanceof ExternalHostError) { if (cachedResult) { logger.warn( - { err }, - 'npmjs error, reusing expired cached result instead', + { err, host: uri.host }, + `npm host error, reusing expired cached result instead`, ); delete cachedResult.cacheData; return cachedResult; } - // istanbul ignore if - if (err.name === 'ParseError' && err.body) { - err.body = 'err.body deleted by Renovate'; + + if (actualError.name === 'ParseError' && actualError.body) { + actualError.body = 'err.body deleted by Renovate'; + err.err = actualError; } - throw new ExternalHostError(err); + throw err; } logger.debug({ err }, 'Unknown npm lookup error'); return null; diff --git a/lib/modules/datasource/npm/index.ts b/lib/modules/datasource/npm/index.ts index 84795f0f7bc14f..65a4df8c1b8d7a 100644 --- a/lib/modules/datasource/npm/index.ts +++ b/lib/modules/datasource/npm/index.ts @@ -14,6 +14,13 @@ export class NpmDatasource extends Datasource { override readonly defaultVersioning = npmVersioning.id; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `time` field in the results.'; + override readonly sourceUrlSupport = 'release'; + override readonly sourceUrlNote = + 'The source URL is determined from the `repository` field in the results.'; + constructor() { super(NpmDatasource.id); } diff --git a/lib/modules/datasource/npm/npmrc.ts b/lib/modules/datasource/npm/npmrc.ts index ad605fe4ff2bc7..73b2e1eef52896 100644 --- a/lib/modules/datasource/npm/npmrc.ts +++ b/lib/modules/datasource/npm/npmrc.ts @@ -8,7 +8,7 @@ import type { HostRule } from '../../../types'; import * as hostRules from '../../../util/host-rules'; import { regEx } from '../../../util/regex'; import { fromBase64 } from '../../../util/string'; -import { ensureTrailingSlash, validateUrl } from '../../../util/url'; +import { ensureTrailingSlash, isHttpUrl } from '../../../util/url'; import { defaultRegistryUrls } from './common'; import type { NpmrcRules } from './types'; @@ -89,7 +89,7 @@ export function convertNpmrcToRules(npmrc: Record): NpmrcRules { const { registry } = npmrc; // packageRules order matters, so look for a default registry first if (is.nonEmptyString(registry)) { - if (validateUrl(registry)) { + if (isHttpUrl(registry)) { // Default registry rules.packageRules?.push({ matchDatasources, @@ -108,7 +108,7 @@ export function convertNpmrcToRules(npmrc: Record): NpmrcRules { const keyType = keyParts.pop(); if (keyType === 'registry' && keyParts.length && is.nonEmptyString(value)) { const scope = keyParts.join(':'); - if (validateUrl(value)) { + if (isHttpUrl(value)) { rules.packageRules?.push({ matchDatasources, matchPackagePrefixes: [scope + '/'], diff --git a/lib/modules/datasource/npm/types.ts b/lib/modules/datasource/npm/types.ts index e67dd987a0e0ef..2cd011d84bc072 100644 --- a/lib/modules/datasource/npm/types.ts +++ b/lib/modules/datasource/npm/types.ts @@ -35,6 +35,7 @@ export interface NpmResponse { export interface CachedReleaseResult extends ReleaseResult { cacheData?: { + revision?: number; etag: string | undefined; softExpireAt: string; }; diff --git a/lib/modules/datasource/nuget/__fixtures__/nlog/NLog.4.7.3-no-repo.nupkg b/lib/modules/datasource/nuget/__fixtures__/nlog/NLog.4.7.3-no-repo.nupkg new file mode 100644 index 00000000000000..77ce3d810f1ade Binary files /dev/null and b/lib/modules/datasource/nuget/__fixtures__/nlog/NLog.4.7.3-no-repo.nupkg differ diff --git a/lib/modules/datasource/nuget/__fixtures__/nlog/NLog.4.7.3.nupkg b/lib/modules/datasource/nuget/__fixtures__/nlog/NLog.4.7.3.nupkg new file mode 100644 index 00000000000000..6ec85f1ec04651 Binary files /dev/null and b/lib/modules/datasource/nuget/__fixtures__/nlog/NLog.4.7.3.nupkg differ diff --git a/lib/modules/datasource/nuget/v3.spec.ts b/lib/modules/datasource/nuget/common.spec.ts similarity index 85% rename from lib/modules/datasource/nuget/v3.spec.ts rename to lib/modules/datasource/nuget/common.spec.ts index 0a2bd8f315ec53..8296ca85e35345 100644 --- a/lib/modules/datasource/nuget/v3.spec.ts +++ b/lib/modules/datasource/nuget/common.spec.ts @@ -1,6 +1,6 @@ -import { sortNugetVersions } from './v3'; +import { sortNugetVersions } from './common'; -describe('modules/datasource/nuget/v3', () => { +describe('modules/datasource/nuget/common', () => { it.each<{ version: string; other: string; result: number }>` version | other | result ${'invalid1'} | ${'invalid2'} | ${0} diff --git a/lib/modules/datasource/nuget/common.ts b/lib/modules/datasource/nuget/common.ts index 55ce19a78f768b..14bb38d6942111 100644 --- a/lib/modules/datasource/nuget/common.ts +++ b/lib/modules/datasource/nuget/common.ts @@ -1,6 +1,7 @@ import { logger } from '../../../logger'; import { regEx } from '../../../util/regex'; import { parseUrl } from '../../../util/url'; +import { api as versioning } from '../../versioning/nuget'; import type { ParsedRegistryUrl } from './types'; const buildMetaRe = regEx(/\+.+$/g); @@ -11,10 +12,14 @@ export function removeBuildMeta(version: string): string { const urlWhitespaceRe = regEx(/\s/g); -export function massageUrl(url: string): string { +export function massageUrl(url: string | null | undefined): string | null { + if (url === null || url === undefined) { + return null; + } + let resultUrl = url; - // During `dotnet pack` certain URLs are being URL decoded which may introduce whitespaces + // During `dotnet pack` certain URLs are being URL decoded which may introduce whitespace // and causes Markdown link generation problems. resultUrl = resultUrl.replace(urlWhitespaceRe, '%20'); @@ -47,3 +52,23 @@ export function parseRegistryUrl(registryUrl: string): ParsedRegistryUrl { const feedUrl = parsedUrl.href; return { feedUrl, protocolVersion }; } + +/** + * Compare two versions. Return: + * - `1` if `a > b` or `b` is invalid + * - `-1` if `a < b` or `a` is invalid + * - `0` if `a == b` or both `a` and `b` are invalid + */ +export function sortNugetVersions(a: string, b: string): number { + if (versioning.isValid(a)) { + if (versioning.isValid(b)) { + return versioning.sortVersions(a, b); + } else { + return 1; + } + } else if (versioning.isValid(b)) { + return -1; + } else { + return 0; + } +} diff --git a/lib/modules/datasource/nuget/index.spec.ts b/lib/modules/datasource/nuget/index.spec.ts index db7bbaa7b52d61..ce25cf7722b9c0 100644 --- a/lib/modules/datasource/nuget/index.spec.ts +++ b/lib/modules/datasource/nuget/index.spec.ts @@ -1,8 +1,12 @@ +import { Readable } from 'stream'; import { mockDeep } from 'jest-mock-extended'; +import { join } from 'upath'; import { getPkgReleases } from '..'; import { Fixtures } from '../../../../test/fixtures'; import * as httpMock from '../../../../test/http-mock'; -import { logger } from '../../../../test/util'; +import { logger, mocked } from '../../../../test/util'; +import { GlobalConfig } from '../../../config/global'; +import * as _packageCache from '../../../util/cache/package'; import * as _hostRules from '../../../util/host-rules'; import { id as versioning } from '../../versioning/nuget'; import { parseRegistryUrl } from './common'; @@ -14,6 +18,9 @@ const hostRules: any = _hostRules; jest.mock('../../../util/host-rules', () => mockDeep()); +jest.mock('../../../util/cache/package', () => mockDeep()); +const packageCache = mocked(_packageCache); + const pkgInfoV3FromNuget = Fixtures.get('nunit/v3_nuget_org.xml'); const pkgListV3Registration = Fixtures.get('nunit/v3_registration.json'); @@ -105,6 +112,10 @@ const configV3AzureDevOps = { }; describe('modules/datasource/nuget/index', () => { + beforeEach(() => { + GlobalConfig.reset(); + }); + describe('parseRegistryUrl', () => { it('extracts feed version from registry URL hash (v3)', () => { const parsed = parseRegistryUrl('https://my-registry#protocolVersion=3'); @@ -302,6 +313,160 @@ describe('modules/datasource/nuget/index', () => { ); }); + describe('determine source URL from nupkg', () => { + beforeEach(() => { + GlobalConfig.set({ + cacheDir: join('/tmp/cache'), + }); + process.env.RENOVATE_X_NUGET_DOWNLOAD_NUPKGS = 'true'; + }); + + afterEach(() => { + delete process.env.RENOVATE_X_NUGET_DOWNLOAD_NUPKGS; + }); + + it('can determine source URL from nupkg when PackageBaseAddress is missing', async () => { + const nugetIndex = ` + { + "version": "3.0.0", + "resources": [ + { + "@id": "https://some-registry/v3/metadata", + "@type": "RegistrationsBaseUrl/3.0.0-beta", + "comment": "Get package metadata." + } + ] + } + `; + const nlogRegistration = ` + { + "count": 1, + "items": [ + { + "@id": "https://some-registry/v3/metadata/nlog/4.7.3.json", + "lower": "4.7.3", + "upper": "4.7.3", + "count": 1, + "items": [ + { + "@id": "foo", + "catalogEntry": { + "id": "NLog", + "version": "4.7.3", + "packageContent": "https://some-registry/v3-flatcontainer/nlog/4.7.3/nlog.4.7.3.nupkg" + } + } + ] + } + ] + } + `; + httpMock + .scope('https://some-registry') + .get('/v3/index.json') + .twice() + .reply(200, nugetIndex) + .get('/v3/metadata/nlog/index.json') + .reply(200, nlogRegistration) + .get('/v3-flatcontainer/nlog/4.7.3/nlog.4.7.3.nupkg') + .reply(200, () => { + const readableStream = new Readable(); + readableStream.push(Fixtures.getBinary('nlog/NLog.4.7.3.nupkg')); + readableStream.push(null); + return readableStream; + }); + const res = await getPkgReleases({ + datasource, + versioning, + packageName: 'NLog', + registryUrls: ['https://some-registry/v3/index.json'], + }); + expect(logger.logger.debug).toHaveBeenCalledWith( + 'Determined sourceUrl https://github.com/NLog/NLog.git from https://some-registry/v3-flatcontainer/nlog/4.7.3/nlog.4.7.3.nupkg', + ); + expect(packageCache.set).toHaveBeenCalledWith( + 'datasource-nuget', + 'cache-decorator:source-url:https://some-registry/v3/index.json:NLog', + { + cachedAt: expect.any(String), + value: 'https://github.com/NLog/NLog.git', + }, + 60 * 24 * 7, + ); + expect(res?.sourceUrl).toBeDefined(); + }); + + it('can handle nupkg without repository metadata', async () => { + const nugetIndex = ` + { + "version": "3.0.0", + "resources": [ + { + "@id": "https://some-registry/v3/metadata", + "@type": "RegistrationsBaseUrl/3.0.0-beta", + "comment": "Get package metadata." + } + ] + } + `; + const nlogRegistration = ` + { + "count": 1, + "items": [ + { + "@id": "https://some-registry/v3/metadata/nlog/4.7.3.json", + "lower": "4.7.3", + "upper": "4.7.3", + "count": 1, + "items": [ + { + "@id": "foo", + "catalogEntry": { + "id": "NLog", + "version": "4.7.3", + "packageContent": "https://some-registry/v3-flatcontainer/nlog/4.7.3/nlog.4.7.3.nupkg" + } + } + ] + } + ] + } + `; + httpMock + .scope('https://some-registry') + .get('/v3/index.json') + .twice() + .reply(200, nugetIndex) + .get('/v3/metadata/nlog/index.json') + .reply(200, nlogRegistration) + .get('/v3-flatcontainer/nlog/4.7.3/nlog.4.7.3.nupkg') + .reply(200, () => { + const readableStream = new Readable(); + readableStream.push( + Fixtures.getBinary('nlog/NLog.4.7.3-no-repo.nupkg'), + ); + readableStream.push(null); + return readableStream; + }); + const res = await getPkgReleases({ + datasource, + versioning, + packageName: 'NLog', + registryUrls: ['https://some-registry/v3/index.json'], + }); + expect(packageCache.set).toHaveBeenCalledWith( + 'datasource-nuget', + 'cache-decorator:source-url:https://some-registry/v3/index.json:NLog', + { + cachedAt: expect.any(String), + value: null, + }, + 60 * 24 * 7, + ); + expect(res?.sourceUrl).toBeUndefined(); + }); + }); + it('returns null for non 200 (v3v2)', async () => { httpMock.scope('https://api.nuget.org').get('/v3/index.json').reply(500); httpMock diff --git a/lib/modules/datasource/nuget/index.ts b/lib/modules/datasource/nuget/index.ts index a34bcb00881adc..1307901f72ef36 100644 --- a/lib/modules/datasource/nuget/index.ts +++ b/lib/modules/datasource/nuget/index.ts @@ -3,8 +3,8 @@ import * as nugetVersioning from '../../versioning/nuget'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, ReleaseResult } from '../types'; import { parseRegistryUrl } from './common'; -import * as v2 from './v2'; -import * as v3 from './v3'; +import { NugetV2Api } from './v2'; +import { NugetV3Api } from './v3'; // https://api.nuget.org/v3/index.json is a default official nuget feed export const nugetOrg = 'https://api.nuget.org/v3/index.json'; @@ -18,6 +18,21 @@ export class NugetDatasource extends Datasource { override readonly registryStrategy = 'merge'; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = ` + For the v2 API, the release timestamp is determined from the \`Publised\` tag and, + for the v3 API, the release timestamp is determined from the \`published\` field in the results. + `; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = ` + For the v2 API, the source URL is determined from the \`ProjectUrl\` tag and, + for the v3 API, the source URL is determined from the \`metadata.repository@url\` field in the results. + `; + + readonly v2Api = new NugetV2Api(); + + readonly v3Api = new NugetV3Api(); + constructor() { super(NugetDatasource.id); } @@ -33,12 +48,17 @@ export class NugetDatasource extends Datasource { } const { feedUrl, protocolVersion } = parseRegistryUrl(registryUrl); if (protocolVersion === 2) { - return v2.getReleases(this.http, feedUrl, packageName); + return this.v2Api.getReleases(this.http, feedUrl, packageName); } if (protocolVersion === 3) { - const queryUrl = await v3.getResourceUrl(this.http, feedUrl); + const queryUrl = await this.v3Api.getResourceUrl(this.http, feedUrl); if (queryUrl) { - return v3.getReleases(this.http, feedUrl, queryUrl, packageName); + return this.v3Api.getReleases( + this.http, + feedUrl, + queryUrl, + packageName, + ); } } return null; diff --git a/lib/modules/datasource/nuget/types.ts b/lib/modules/datasource/nuget/types.ts index 29aba2a5c6b4a5..36fa672e83b223 100644 --- a/lib/modules/datasource/nuget/types.ts +++ b/lib/modules/datasource/nuget/types.ts @@ -5,11 +5,13 @@ export interface ServicesIndexRaw { }[]; } +// See https://learn.microsoft.com/en-us/nuget/api/registration-base-url-resource#catalog-entry export interface CatalogEntry { version: string; published?: string; projectUrl?: string; listed?: boolean; + packageContent?: string; } export interface CatalogPage { diff --git a/lib/modules/datasource/nuget/v2.ts b/lib/modules/datasource/nuget/v2.ts index 71629f90f8c436..618cd30c1283e3 100644 --- a/lib/modules/datasource/nuget/v2.ts +++ b/lib/modules/datasource/nuget/v2.ts @@ -1,70 +1,74 @@ import { XmlDocument, XmlElement } from 'xmldoc'; import { logger } from '../../../logger'; import type { Http } from '../../../util/http'; -import type { HttpResponse } from '../../../util/http/types'; import { regEx } from '../../../util/regex'; import type { ReleaseResult } from '../types'; import { massageUrl, removeBuildMeta } from './common'; -function getPkgProp(pkgInfo: XmlElement, propName: string): string | undefined { - return pkgInfo.childNamed('m:properties')?.childNamed(`d:${propName}`)?.val; -} +export class NugetV2Api { + getPkgProp(pkgInfo: XmlElement, propName: string): string | undefined { + return pkgInfo.childNamed('m:properties')?.childNamed(`d:${propName}`)?.val; + } -export async function getReleases( - http: Http, - feedUrl: string, - pkgName: string, -): Promise { - const dep: ReleaseResult = { - releases: [], - }; - let pkgUrlList: string | null = `${feedUrl.replace( - regEx(/\/+$/), - '', - )}/FindPackagesById()?id=%27${pkgName}%27&$select=Version,IsLatestVersion,ProjectUrl,Published`; - while (pkgUrlList !== null) { - // typescript issue - const pkgVersionsListRaw: HttpResponse = await http.get(pkgUrlList); - const pkgVersionsListDoc = new XmlDocument(pkgVersionsListRaw.body); + async getReleases( + http: Http, + feedUrl: string, + pkgName: string, + ): Promise { + const dep: ReleaseResult = { + releases: [], + }; + let pkgUrlList: string | null = `${feedUrl.replace( + regEx(/\/+$/), + '', + )}/FindPackagesById()?id=%27${pkgName}%27&$select=Version,IsLatestVersion,ProjectUrl,Published`; + while (pkgUrlList !== null) { + // typescript issue + const pkgVersionsListRaw = await http.get(pkgUrlList); + const pkgVersionsListDoc = new XmlDocument(pkgVersionsListRaw.body); - const pkgInfoList = pkgVersionsListDoc.childrenNamed('entry'); + const pkgInfoList = pkgVersionsListDoc.childrenNamed('entry'); - for (const pkgInfo of pkgInfoList) { - const version = getPkgProp(pkgInfo, 'Version'); - const releaseTimestamp = getPkgProp(pkgInfo, 'Published'); - dep.releases.push({ - // TODO: types (#22198) - version: removeBuildMeta(`${version}`), - releaseTimestamp, - }); - try { - const pkgIsLatestVersion = getPkgProp(pkgInfo, 'IsLatestVersion'); - if (pkgIsLatestVersion === 'true') { - dep['tags'] = { latest: removeBuildMeta(`${version}`) }; - const projectUrl = getPkgProp(pkgInfo, 'ProjectUrl'); - if (projectUrl) { - dep.sourceUrl = massageUrl(projectUrl); + for (const pkgInfo of pkgInfoList) { + const version = this.getPkgProp(pkgInfo, 'Version'); + const releaseTimestamp = this.getPkgProp(pkgInfo, 'Published'); + dep.releases.push({ + // TODO: types (#22198) + version: removeBuildMeta(`${version}`), + releaseTimestamp, + }); + try { + const pkgIsLatestVersion = this.getPkgProp( + pkgInfo, + 'IsLatestVersion', + ); + if (pkgIsLatestVersion === 'true') { + dep['tags'] = { latest: removeBuildMeta(`${version}`) }; + const projectUrl = this.getPkgProp(pkgInfo, 'ProjectUrl'); + if (projectUrl) { + dep.sourceUrl = massageUrl(projectUrl); + } } + } catch (err) /* istanbul ignore next */ { + logger.debug( + { err, pkgName, feedUrl }, + `nuget registry failure: can't parse pkg info for project url`, + ); } - } catch (err) /* istanbul ignore next */ { - logger.debug( - { err, pkgName, feedUrl }, - `nuget registry failure: can't parse pkg info for project url`, - ); } - } - const nextPkgUrlListLink = pkgVersionsListDoc - .childrenNamed('link') - .find((node) => node.attr.rel === 'next'); + const nextPkgUrlListLink = pkgVersionsListDoc + .childrenNamed('link') + .find((node) => node.attr.rel === 'next'); - pkgUrlList = nextPkgUrlListLink ? nextPkgUrlListLink.attr.href : null; - } + pkgUrlList = nextPkgUrlListLink ? nextPkgUrlListLink.attr.href : null; + } - // dep not found if no release, so we can try next registry - if (dep.releases.length === 0) { - return null; - } + // dep not found if no release, so we can try next registry + if (dep.releases.length === 0) { + return null; + } - return dep; + return dep; + } } diff --git a/lib/modules/datasource/nuget/v3.ts b/lib/modules/datasource/nuget/v3.ts index 6f53906b3afc6a..1d0dc587c01d3a 100644 --- a/lib/modules/datasource/nuget/v3.ts +++ b/lib/modules/datasource/nuget/v3.ts @@ -1,16 +1,21 @@ import is from '@sindresorhus/is'; +import extract from 'extract-zip'; import semver from 'semver'; +import upath from 'upath'; import { XmlDocument } from 'xmldoc'; import { logger } from '../../../logger'; import { ExternalHostError } from '../../../types/errors/external-host-error'; import * as packageCache from '../../../util/cache/package'; +import { cache } from '../../../util/cache/package/decorator'; +import * as fs from '../../../util/fs'; +import { ensureCacheDir } from '../../../util/fs'; import { Http, HttpError } from '../../../util/http'; import * as p from '../../../util/promises'; import { regEx } from '../../../util/regex'; import { ensureTrailingSlash } from '../../../util/url'; import { api as versioning } from '../../versioning/nuget'; import type { Release, ReleaseResult } from '../types'; -import { massageUrl, removeBuildMeta } from './common'; +import { massageUrl, removeBuildMeta, sortNugetVersions } from './common'; import type { CatalogEntry, CatalogPage, @@ -18,227 +23,287 @@ import type { ServicesIndexRaw, } from './types'; -const cacheNamespace = 'datasource-nuget'; - -export async function getResourceUrl( - http: Http, - url: string, - resourceType = 'RegistrationsBaseUrl', -): Promise { - // https://docs.microsoft.com/en-us/nuget/api/service-index - const resultCacheKey = `${url}:${resourceType}`; - const cachedResult = await packageCache.get( - cacheNamespace, - resultCacheKey, - ); - - // istanbul ignore if - if (cachedResult) { - return cachedResult; - } - let servicesIndexRaw: ServicesIndexRaw | undefined; - try { - const responseCacheKey = url; - servicesIndexRaw = await packageCache.get( - cacheNamespace, - responseCacheKey, +export class NugetV3Api { + static readonly cacheNamespace = 'datasource-nuget'; + + async getResourceUrl( + http: Http, + url: string, + resourceType = 'RegistrationsBaseUrl', + ): Promise { + // https://docs.microsoft.com/en-us/nuget/api/service-index + const resultCacheKey = `${url}:${resourceType}`; + const cachedResult = await packageCache.get( + NugetV3Api.cacheNamespace, + resultCacheKey, ); - // istanbul ignore else: currently not testable - if (!servicesIndexRaw) { - servicesIndexRaw = (await http.getJson(url)).body; - await packageCache.set( - cacheNamespace, + + // istanbul ignore if + if (cachedResult) { + return cachedResult; + } + let servicesIndexRaw: ServicesIndexRaw | undefined; + try { + const responseCacheKey = url; + servicesIndexRaw = await packageCache.get( + NugetV3Api.cacheNamespace, responseCacheKey, - servicesIndexRaw, - 3 * 24 * 60, ); - } + // istanbul ignore else: currently not testable + if (!servicesIndexRaw) { + servicesIndexRaw = (await http.getJson(url)).body; + await packageCache.set( + NugetV3Api.cacheNamespace, + responseCacheKey, + servicesIndexRaw, + 3 * 24 * 60, + ); + } + + const services = servicesIndexRaw.resources + .map(({ '@id': serviceId, '@type': t }) => ({ + serviceId, + type: t?.split('/')?.shift(), + version: t?.split('/')?.pop(), + })) + .filter( + ({ type, version }) => type === resourceType && semver.valid(version), + ) + .sort((x, y) => + x.version && y.version + ? semver.compare(x.version, y.version) + : /* istanbul ignore next: hard to test */ 0, + ); + + if (services.length === 0) { + await packageCache.set( + NugetV3Api.cacheNamespace, + resultCacheKey, + null, + 60, + ); + logger.debug( + { url, servicesIndexRaw }, + `no ${resourceType} services found`, + ); + return null; + } + + const { serviceId, version } = services.pop()!; + + // istanbul ignore if + if ( + resourceType === 'RegistrationsBaseUrl' && + version && + !version.startsWith('3.0.0-') && + !semver.satisfies(version, '^3.0.0') + ) { + logger.warn( + { url, version }, + `Nuget: Unknown version returned. Only v3 is supported`, + ); + } - const services = servicesIndexRaw.resources - .map(({ '@id': serviceId, '@type': t }) => ({ + await packageCache.set( + NugetV3Api.cacheNamespace, + resultCacheKey, serviceId, - type: t?.split('/')?.shift(), - version: t?.split('/')?.pop(), - })) - .filter( - ({ type, version }) => type === resourceType && semver.valid(version), - ) - .sort((x, y) => - x.version && y.version - ? semver.compare(x.version, y.version) - : /* istanbul ignore next: hard to test */ 0, + 60, ); - - if (services.length === 0) { - await packageCache.set(cacheNamespace, resultCacheKey, null, 60); + return serviceId; + } catch (err) { + // istanbul ignore if: not easy testable with nock + if (err instanceof ExternalHostError) { + throw err; + } logger.debug( - { url, servicesIndexRaw }, - `no ${resourceType} services found`, + { err, url, servicesIndexRaw }, + `nuget registry failure: can't get ${resourceType}`, ); return null; } + } - const { serviceId, version } = services.pop()!; - - // istanbul ignore if - if ( - resourceType === 'RegistrationsBaseUrl' && - version && - !version.startsWith('3.0.0-') && - !semver.satisfies(version, '^3.0.0') - ) { - logger.warn( - { url, version }, - `Nuget: Unknown version returned. Only v3 is supported`, - ); + async getCatalogEntry( + http: Http, + catalogPage: CatalogPage, + ): Promise { + let items = catalogPage.items; + if (!items) { + const url = catalogPage['@id']; + const catalogPageFull = await http.getJson(url); + items = catalogPageFull.body.items; } + return items.map(({ catalogEntry }) => catalogEntry); + } - await packageCache.set(cacheNamespace, resultCacheKey, serviceId, 60); - return serviceId; - } catch (err) { - // istanbul ignore if: not easy testable with nock - if (err instanceof ExternalHostError) { - throw err; - } - logger.debug( - { err, url, servicesIndexRaw }, - `nuget registry failure: can't get ${resourceType}`, + async getReleases( + http: Http, + registryUrl: string, + feedUrl: string, + pkgName: string, + ): Promise { + const baseUrl = feedUrl.replace(regEx(/\/*$/), ''); + const url = `${baseUrl}/${pkgName.toLowerCase()}/index.json`; + const packageRegistration = await http.getJson(url); + const catalogPages = packageRegistration.body.items || []; + const catalogPagesQueue = catalogPages.map( + (page) => (): Promise => this.getCatalogEntry(http, page), ); - return null; - } -} + const catalogEntries = (await p.all(catalogPagesQueue)) + .flat() + .sort((a, b) => sortNugetVersions(a.version, b.version)); -async function getCatalogEntry( - http: Http, - catalogPage: CatalogPage, -): Promise { - let items = catalogPage.items; - if (!items) { - const url = catalogPage['@id']; - const catalogPageFull = await http.getJson(url); - items = catalogPageFull.body.items; - } - return items.map(({ catalogEntry }) => catalogEntry); -} + let homepage: string | null = null; + let latestStable: string | null = null; + let nupkgUrl: string | null = null; + const releases = catalogEntries.map( + ({ + version, + published: releaseTimestamp, + projectUrl, + listed, + packageContent, + }) => { + const release: Release = { version: removeBuildMeta(version) }; + if (releaseTimestamp) { + release.releaseTimestamp = releaseTimestamp; + } + if (versioning.isValid(version) && versioning.isStable(version)) { + latestStable = removeBuildMeta(version); + homepage = projectUrl ? massageUrl(projectUrl) : homepage; + nupkgUrl = massageUrl(packageContent); + } + if (listed === false) { + release.isDeprecated = true; + } + return release; + }, + ); -/** - * Compare two versions. Return: - * - `1` if `a > b` or `b` is invalid - * - `-1` if `a < b` or `a` is invalid - * - `0` if `a == b` or both `a` and `b` are invalid - */ -export function sortNugetVersions(a: string, b: string): number { - if (versioning.isValid(a)) { - if (versioning.isValid(b)) { - return versioning.sortVersions(a, b); - } else { - return 1; + if (!releases.length) { + return null; } - } else if (versioning.isValid(b)) { - return -1; - } else { - return 0; - } -} - -export async function getReleases( - http: Http, - registryUrl: string, - feedUrl: string, - pkgName: string, -): Promise { - const baseUrl = feedUrl.replace(regEx(/\/*$/), ''); - const url = `${baseUrl}/${pkgName.toLowerCase()}/index.json`; - const packageRegistration = await http.getJson(url); - const catalogPages = packageRegistration.body.items || []; - const catalogPagesQueue = catalogPages.map( - (page) => (): Promise => getCatalogEntry(http, page), - ); - const catalogEntries = (await p.all(catalogPagesQueue)) - .flat() - .sort((a, b) => sortNugetVersions(a.version, b.version)); - - let homepage: string | null = null; - let latestStable: string | null = null; - const releases = catalogEntries.map( - ({ version, published: releaseTimestamp, projectUrl, listed }) => { - const release: Release = { version: removeBuildMeta(version) }; - if (releaseTimestamp) { - release.releaseTimestamp = releaseTimestamp; - } - if (versioning.isValid(version) && versioning.isStable(version)) { - latestStable = removeBuildMeta(version); - homepage = projectUrl ? massageUrl(projectUrl) : homepage; - } - if (listed === false) { - release.isDeprecated = true; - } - return release; - }, - ); - if (!releases.length) { - return null; - } - - // istanbul ignore next: only happens when no stable version exists - if (latestStable === null && catalogPages.length) { - const last = catalogEntries.pop()!; - latestStable = removeBuildMeta(last.version); - homepage ??= last.projectUrl ?? null; - } + // istanbul ignore next: only happens when no stable version exists + if (latestStable === null && catalogPages.length) { + const last = catalogEntries.pop()!; + latestStable = removeBuildMeta(last.version); + homepage ??= last.projectUrl ?? null; + nupkgUrl ??= massageUrl(last.packageContent); + } - const dep: ReleaseResult = { - releases, - }; + const dep: ReleaseResult = { + releases, + }; - try { - const packageBaseAddress = await getResourceUrl( - http, - registryUrl, - 'PackageBaseAddress', - ); - // istanbul ignore else: this is a required v3 api - if (is.nonEmptyString(packageBaseAddress)) { - const nuspecUrl = `${ensureTrailingSlash( - packageBaseAddress, - )}${pkgName.toLowerCase()}/${ - // TODO: types (#22198) - latestStable - }/${pkgName.toLowerCase()}.nuspec`; - const metaresult = await http.get(nuspecUrl); - const nuspec = new XmlDocument(metaresult.body); - const sourceUrl = nuspec.valueWithPath('metadata.repository@url'); - if (sourceUrl) { - dep.sourceUrl = massageUrl(sourceUrl); + try { + const packageBaseAddress = await this.getResourceUrl( + http, + registryUrl, + 'PackageBaseAddress', + ); + if (is.nonEmptyString(packageBaseAddress)) { + const nuspecUrl = `${ensureTrailingSlash( + packageBaseAddress, + )}${pkgName.toLowerCase()}/${ + // TODO: types (#22198) + latestStable + }/${pkgName.toLowerCase()}.nuspec`; + const metaresult = await http.get(nuspecUrl); + const nuspec = new XmlDocument(metaresult.body); + const sourceUrl = nuspec.valueWithPath('metadata.repository@url'); + if (sourceUrl) { + dep.sourceUrl = massageUrl(sourceUrl); + } + } else if (nupkgUrl) { + const sourceUrl = await this.getSourceUrlFromNupkg( + http, + registryUrl, + pkgName, + latestStable, + nupkgUrl, + ); + if (sourceUrl) { + dep.sourceUrl = massageUrl(sourceUrl); + logger.debug(`Determined sourceUrl ${sourceUrl} from ${nupkgUrl}`); + } + } + } catch (err) { + // istanbul ignore if: not easy testable with nock + if (err instanceof ExternalHostError) { + throw err; + } + // ignore / silence 404. Seen on proget, if remote connector is used and package is not yet cached + if (err instanceof HttpError && err.response?.statusCode === 404) { + logger.debug( + { registryUrl, pkgName, pkgVersion: latestStable }, + `package manifest (.nuspec) not found`, + ); + return dep; } - } - } catch (err) { - // istanbul ignore if: not easy testable with nock - if (err instanceof ExternalHostError) { - throw err; - } - // ignore / silence 404. Seen on proget, if remote connector is used and package is not yet cached - if (err instanceof HttpError && err.response?.statusCode === 404) { logger.debug( - { registryUrl, pkgName, pkgVersion: latestStable }, - `package manifest (.nuspec) not found`, + { err, registryUrl, pkgName, pkgVersion: latestStable }, + `Cannot obtain sourceUrl`, ); return dep; } - logger.debug( - { err, registryUrl, pkgName, pkgVersion: latestStable }, - `Cannot obtain sourceUrl`, - ); + + // istanbul ignore else: not easy testable + if (homepage) { + // only assign if not assigned + dep.sourceUrl ??= homepage; + dep.homepage ??= homepage; + } + return dep; } - // istanbul ignore else: not easy testable - if (homepage) { - // only assign if not assigned - dep.sourceUrl ??= homepage; - dep.homepage ??= homepage; + @cache({ + namespace: NugetV3Api.cacheNamespace, + key: ( + _http: Http, + registryUrl: string, + packageName: string, + _packageVersion: string | null, + _nupkgUrl: string, + ) => `source-url:${registryUrl}:${packageName}`, + ttlMinutes: 10080, // 1 week + }) + async getSourceUrlFromNupkg( + http: Http, + _registryUrl: string, + packageName: string, + packageVersion: string | null, + nupkgUrl: string, + ): Promise { + // istanbul ignore if: experimental feature + if (!process.env.RENOVATE_X_NUGET_DOWNLOAD_NUPKGS) { + logger.once.debug('RENOVATE_X_NUGET_DOWNLOAD_NUPKGS is not set'); + return null; + } + const cacheDir = await ensureCacheDir('nuget'); + const nupkgFile = upath.join( + cacheDir, + `${packageName}.${packageVersion}.nupkg`, + ); + const nupkgContentsDir = upath.join( + cacheDir, + `${packageName}.${packageVersion}`, + ); + const readStream = http.stream(nupkgUrl); + try { + const writeStream = fs.createCacheWriteStream(nupkgFile); + await fs.pipeline(readStream, writeStream); + await extract(nupkgFile, { dir: nupkgContentsDir }); + const nuspecFile = upath.join(nupkgContentsDir, `${packageName}.nuspec`); + const nuspec = new XmlDocument( + await fs.readCacheFile(nuspecFile, 'utf8'), + ); + return nuspec.valueWithPath('metadata.repository@url') ?? null; + } finally { + await fs.rmCache(nupkgFile); + await fs.rmCache(nupkgContentsDir); + } } - - return dep; } diff --git a/lib/modules/datasource/orb/index.ts b/lib/modules/datasource/orb/index.ts index 98f0a2c7e8cdf4..2f2a1c20a711cd 100644 --- a/lib/modules/datasource/orb/index.ts +++ b/lib/modules/datasource/orb/index.ts @@ -29,6 +29,10 @@ export class OrbDatasource extends Datasource { override readonly defaultRegistryUrls = ['https://circleci.com/']; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `createdAt` field in the results.'; + @cache({ namespace: `datasource-${OrbDatasource.id}`, key: ({ packageName }: GetReleasesConfig) => packageName, diff --git a/lib/modules/datasource/packagist/index.ts b/lib/modules/datasource/packagist/index.ts index 1c4d4719ae717e..f427929c1e3051 100644 --- a/lib/modules/datasource/packagist/index.ts +++ b/lib/modules/datasource/packagist/index.ts @@ -32,6 +32,14 @@ export class PackagistDatasource extends Datasource { override readonly registryStrategy = 'hunt'; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `time` field in the results.'; + // Note: this can be changed to 'release', as the source is present in each release but we remove it while processing + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined from `source` field in the results.'; + // We calculate auth at this datasource layer so that we can know whether it's safe to cache or not private static getHostOpts(url: string): HttpOptions { const { username, password } = hostRules.find({ diff --git a/lib/modules/datasource/puppet-forge/index.ts b/lib/modules/datasource/puppet-forge/index.ts index fed7e182eca36e..f656f4d5faad79 100644 --- a/lib/modules/datasource/puppet-forge/index.ts +++ b/lib/modules/datasource/puppet-forge/index.ts @@ -12,6 +12,10 @@ export class PuppetForgeDatasource extends Datasource { override readonly defaultRegistryUrls = [PUPPET_FORGE]; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `created_at` field from the response.'; + async getReleases({ packageName, registryUrl, @@ -50,6 +54,7 @@ export class PuppetForgeDatasource extends Datasource { ): ReleaseResult { const result: ReleaseResult = { releases, + // the homepage url in the fixtures is a github repo, we can use this as sourceUrl homepage: module.homepage_url, }; diff --git a/lib/modules/datasource/pypi/__snapshots__/index.spec.ts.snap b/lib/modules/datasource/pypi/__snapshots__/index.spec.ts.snap index 42dc4dcea02587..1af50f1d6d055b 100644 --- a/lib/modules/datasource/pypi/__snapshots__/index.spec.ts.snap +++ b/lib/modules/datasource/pypi/__snapshots__/index.spec.ts.snap @@ -265,9 +265,6 @@ exports[`modules/datasource/pypi/index getReleases respects constraints 1`] = ` { "registryUrl": "https://pypi.org/pypi", "releases": [ - { - "version": "0.4.0", - }, { "version": "0.4.1", }, diff --git a/lib/modules/datasource/pypi/common.ts b/lib/modules/datasource/pypi/common.ts index 17650cb4c12603..737b6a216e555d 100644 --- a/lib/modules/datasource/pypi/common.ts +++ b/lib/modules/datasource/pypi/common.ts @@ -7,6 +7,6 @@ export function isGitHubRepo(url: string): boolean { } // https://packaging.python.org/en/latest/specifications/name-normalization/ -export function normalizeDepName(name: string): string { +export function normalizePythonDepName(name: string): string { return name.replace(/[-_.]+/g, '-').toLowerCase(); } diff --git a/lib/modules/datasource/pypi/index.ts b/lib/modules/datasource/pypi/index.ts index 9ca84c592f6cee..477525bdd48fa5 100644 --- a/lib/modules/datasource/pypi/index.ts +++ b/lib/modules/datasource/pypi/index.ts @@ -1,4 +1,5 @@ import url from 'node:url'; +import is from '@sindresorhus/is'; import changelogFilenameRegex from 'changelog-filename-regex'; import { logger } from '../../../logger'; import { coerceArray } from '../../../util/array'; @@ -8,7 +9,7 @@ import { ensureTrailingSlash } from '../../../util/url'; import * as pep440 from '../../versioning/pep440'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; -import { isGitHubRepo, normalizeDepName } from './common'; +import { isGitHubRepo, normalizePythonDepName } from './common'; import type { PypiJSON, PypiJSONRelease, Releases } from './types'; export class PypiDatasource extends Datasource { @@ -30,6 +31,12 @@ export class PypiDatasource extends Datasource { override readonly registryStrategy = 'merge'; + override readonly releaseTimestampNote = + 'The relase timestamp is determined from the `upload_time` field in the results.'; + override readonly sourceUrlSupport = 'release'; + override readonly sourceUrlNote = + 'The source URL is determined from the `homepage` field if it is a github repository, else we use the `project_urls` field.'; + async getReleases({ packageName, registryUrl, @@ -85,7 +92,7 @@ export class PypiDatasource extends Datasource { ): Promise { const lookupUrl = url.resolve( hostUrl, - `${normalizeDepName(packageName)}/json`, + `${normalizePythonDepName(packageName)}/json`, ); const dependency: ReleaseResult = { releases: [] }; logger.trace({ lookupUrl }, 'Pypi api got lookup'); @@ -156,9 +163,11 @@ export class PypiDatasource extends Datasource { result.isDeprecated = isDeprecated; } // There may be multiple releases with different requires_python, so we return all in an array + const pythonConstraints = releases + .map(({ requires_python }) => requires_python) + .filter(is.string); result.constraints = { - // TODO: string[] isn't allowed here - python: releases.map(({ requires_python }) => requires_python) as any, + python: Array.from(new Set(pythonConstraints)), }; return result; }); @@ -223,7 +232,7 @@ export class PypiDatasource extends Datasource { ): Promise { const lookupUrl = url.resolve( hostUrl, - ensureTrailingSlash(normalizeDepName(packageName)), + ensureTrailingSlash(normalizePythonDepName(packageName)), ); const dependency: ReleaseResult = { releases: [] }; const response = await this.http.get(lookupUrl); diff --git a/lib/modules/datasource/python-version/__fixtures__/eol.json b/lib/modules/datasource/python-version/__fixtures__/eol.json new file mode 100644 index 00000000000000..4f9d6d19e384d9 --- /dev/null +++ b/lib/modules/datasource/python-version/__fixtures__/eol.json @@ -0,0 +1,4 @@ +[ + {"cycle":"3.12","releaseDate":"2023-10-02","support":"2025-04-02","eol":"2028-10-31","latest":"3.12.2","latestReleaseDate":"2024-02-06","lts":false}, + {"cycle":"3.7","releaseDate":"2018-06-26","support":"2020-06-27","eol":"2023-06-27","latest":"3.7.17","latestReleaseDate":"2023-06-05","lts":false} +] diff --git a/lib/modules/datasource/python-version/__fixtures__/release.json b/lib/modules/datasource/python-version/__fixtures__/release.json new file mode 100644 index 00000000000000..af122e745d4e87 --- /dev/null +++ b/lib/modules/datasource/python-version/__fixtures__/release.json @@ -0,0 +1,7 @@ +[ + {"name": "Python 3.12.0", "slug": "python-3120", "version": 3, "is_published": true, "is_latest": false, "release_date": "2023-10-02T12:50:09Z", "pre_release": false, "release_page": null, "release_notes_url": "https://docs.python.org/release/3.12.0/whatsnew/changelog.html#python-3-12-0", "show_on_download_page": true, "resource_uri": "https://www.python.org/api/v2/downloads/release/832/"}, + {"name": "Python 3.12.0a1", "slug": "python-3120a1", "version": 3, "is_published": true, "is_latest": false, "release_date": "2022-10-25T02:16:12Z", "pre_release": true, "release_page": null, "release_notes_url": "", "show_on_download_page": false, "resource_uri": "https://www.python.org/api/v2/downloads/release/767/"}, + {"name": "Python 3.12.2", "slug": "python-3122", "version": 3, "is_published": true, "is_latest": true, "release_date": "2024-02-06T21:40:35Z", "pre_release": false, "release_page": null, "release_notes_url": "https://docs.python.org/release/3.12.2/whatsnew/changelog.html#python-3-12-2", "show_on_download_page": true, "resource_uri": "https://www.python.org/api/v2/downloads/release/871/"}, + {"name": "Python 3.7.8", "slug": "python-378", "version": 3, "is_published": true, "is_latest": false, "release_date": "2020-06-27T12:55:01Z", "pre_release": false, "release_page": null, "release_notes_url": "https://docs.python.org/release/3.7.8/whatsnew/changelog.html#changelog", "show_on_download_page": true, "resource_uri": "https://www.python.org/api/v2/downloads/release/442/"}, + {"name": "Python 3.7.9", "slug": "python-379", "version": 3, "is_published": true, "is_latest": false, "release_date": "2020-08-17T22:00:00Z", "pre_release": false, "release_page": null, "release_notes_url": "https://docs.python.org/release/3.7.9/whatsnew/changelog.html#changelog", "show_on_download_page": true, "resource_uri": "https://www.python.org/api/v2/downloads/release/482/"} +] diff --git a/lib/modules/datasource/python-version/common.ts b/lib/modules/datasource/python-version/common.ts new file mode 100644 index 00000000000000..304693714f1d7d --- /dev/null +++ b/lib/modules/datasource/python-version/common.ts @@ -0,0 +1,5 @@ +export const defaultRegistryUrl = + 'https://www.python.org/api/v2/downloads/release'; +export const githubBaseUrl = 'https://api.github.com/'; + +export const datasource = 'python-version'; diff --git a/lib/modules/datasource/python-version/index.spec.ts b/lib/modules/datasource/python-version/index.spec.ts new file mode 100644 index 00000000000000..4333bfe0cec344 --- /dev/null +++ b/lib/modules/datasource/python-version/index.spec.ts @@ -0,0 +1,148 @@ +import { satisfies } from '@renovatebot/pep440'; +import { getPkgReleases } from '..'; +import { Fixtures } from '../../../../test/fixtures'; +import * as httpMock from '../../../../test/http-mock'; +import { EXTERNAL_HOST_ERROR } from '../../../constants/error-messages'; +import * as githubGraphql from '../../../util/github/graphql'; +import { registryUrl as eolRegistryUrl } from '../endoflife-date/common'; +import { datasource, defaultRegistryUrl } from './common'; +import { PythonVersionDatasource } from '.'; + +describe('modules/datasource/python-version/index', () => { + describe('dependent datasources', () => { + it('returns Python EOL data', async () => { + const datasource = new PythonVersionDatasource(); + httpMock + .scope(eolRegistryUrl) + .get('/python.json') + .reply(200, Fixtures.get('eol.json')); + const res = await datasource.getEolReleases(); + expect( + res?.releases.find((release) => release.version === '3.7.17') + ?.isDeprecated, + ).toBeTrue(); + }); + }); + + describe('getReleases', () => { + beforeEach(() => { + httpMock + .scope('https://endoflife.date') + .get('/api/python.json') + .reply(200, Fixtures.get('eol.json')); + + jest.spyOn(githubGraphql, 'queryReleases').mockResolvedValueOnce([ + { + id: 1, + url: 'https://example.com', + name: 'containerbase/python-prebuild', + description: 'some description', + version: '3.12.1', + releaseTimestamp: '2020-03-09T13:00:00Z', + }, + { + id: 2, + url: 'https://example.com', + name: 'containerbase/python-prebuild', + description: 'some description', + version: '3.12.0', + releaseTimestamp: '2020-03-09T13:00:00Z', + }, + { + id: 3, + url: 'https://example.com', + name: 'containerbase/python-prebuild', + description: 'some description', + version: '3.7.8', + releaseTimestamp: '2020-03-09T13:00:00Z', + }, + ]); + }); + + it('throws for 500', async () => { + httpMock.scope(defaultRegistryUrl).get('').reply(500); + await expect( + getPkgReleases({ + datasource, + packageName: 'python', + }), + ).rejects.toThrow(EXTERNAL_HOST_ERROR); + }); + + it('returns null for error', async () => { + httpMock.scope(defaultRegistryUrl).get('').replyWithError('error'); + expect( + await getPkgReleases({ + datasource, + packageName: 'python', + }), + ).toBeNull(); + }); + + it('returns null for empty 200 OK', async () => { + httpMock.scope(defaultRegistryUrl).get('').reply(200, []); + expect( + await getPkgReleases({ + datasource, + packageName: 'python', + }), + ).toBeNull(); + }); + + describe('processes real data', () => { + beforeEach(() => { + httpMock + .scope(defaultRegistryUrl) + .get('') + .reply(200, Fixtures.get('release.json')); + }); + + it('returns the correct data', async () => { + const res = await getPkgReleases({ + datasource, + packageName: 'python', + }); + expect(res?.releases[0]).toEqual({ + isDeprecated: true, + isStable: true, + releaseTimestamp: '2020-06-27T12:55:01.000Z', + version: '3.7.8', + }); + }); + + it('only returns stable versions', async () => { + const res = await getPkgReleases({ + datasource, + packageName: 'python', + }); + expect(res?.releases).toHaveLength(2); + for (const release of res?.releases ?? []) { + expect(release.isStable).toBeTrue(); + } + }); + + it('only returns versions that are prebuilt', async () => { + const res = await getPkgReleases({ + datasource, + packageName: 'python', + }); + expect( + res?.releases.filter((release) => + satisfies(release.version, '>3.12.1'), + ), + ).toHaveLength(0); + }); + + it('returns isDeprecated status for Python 3 minor releases', async () => { + const res = await getPkgReleases({ + datasource, + packageName: 'python', + }); + expect(res?.releases).toHaveLength(2); + for (const release of res?.releases ?? []) { + expect(release.isDeprecated).toBeBoolean(); + } + }); + }); + }); +}); diff --git a/lib/modules/datasource/python-version/index.ts b/lib/modules/datasource/python-version/index.ts new file mode 100644 index 00000000000000..682c6ceb928747 --- /dev/null +++ b/lib/modules/datasource/python-version/index.ts @@ -0,0 +1,92 @@ +import { cache } from '../../../util/cache/package/decorator'; +import { id as versioning } from '../../versioning/python'; +import { Datasource } from '../datasource'; +import { EndoflifeDatePackagesource } from '../endoflife-date'; +import { registryUrl as eolRegistryUrl } from '../endoflife-date/common'; +import { GithubReleasesDatasource } from '../github-releases'; +import type { GetReleasesConfig, ReleaseResult } from '../types'; +import { datasource, defaultRegistryUrl, githubBaseUrl } from './common'; +import { PythonRelease } from './schema'; + +export class PythonVersionDatasource extends Datasource { + static readonly id = datasource; + pythonPrebuildDatasource: GithubReleasesDatasource; + pythonEolDatasource: EndoflifeDatePackagesource; + + constructor() { + super(datasource); + this.pythonPrebuildDatasource = new GithubReleasesDatasource(); + this.pythonEolDatasource = new EndoflifeDatePackagesource(); + } + + override readonly customRegistrySupport = false; + + override readonly defaultRegistryUrls = [defaultRegistryUrl]; + + override readonly defaultVersioning = versioning; + + override readonly caching = true; + + async getPrebuildReleases(): Promise { + return await this.pythonPrebuildDatasource.getReleases({ + registryUrl: githubBaseUrl, + packageName: 'containerbase/python-prebuild', + }); + } + + async getEolReleases(): Promise { + return await this.pythonEolDatasource.getReleases({ + registryUrl: eolRegistryUrl, + packageName: 'python', + }); + } + + @cache({ + namespace: `datasource-${datasource}`, + key: ({ registryUrl }: GetReleasesConfig) => `${registryUrl}`, + }) + async getReleases({ + registryUrl, + }: GetReleasesConfig): Promise { + // istanbul ignore if + if (!registryUrl) { + return null; + } + const pythonPrebuildReleases = await this.getPrebuildReleases(); + const pythonPrebuildVersions = new Set( + pythonPrebuildReleases?.releases.map((release) => release.version), + ); + const pythonEolReleases = await this.getEolReleases(); + const pythonEolVersions = new Map( + pythonEolReleases?.releases + .filter((release) => release.isDeprecated !== undefined) + .map((release) => [ + release.version.split('.').slice(0, 2).join('.'), + release.isDeprecated, + ]), + ); + const result: ReleaseResult = { + homepage: 'https://python.org', + sourceUrl: 'https://github.com/python/cpython', + registryUrl, + releases: [], + }; + try { + const response = await this.http.getJson(registryUrl, PythonRelease); + result.releases.push( + ...response.body + .filter((release) => release.isStable) + .filter((release) => pythonPrebuildVersions.has(release.version)), + ); + } catch (err) { + this.handleGenericErrors(err); + } + for (const release of result.releases) { + release.isDeprecated = pythonEolVersions.get( + release.version.split('.').slice(0, 2).join('.'), + ); + } + + return result.releases.length ? result : null; + } +} diff --git a/lib/modules/datasource/python-version/readme.md b/lib/modules/datasource/python-version/readme.md new file mode 100644 index 00000000000000..f904048f539810 --- /dev/null +++ b/lib/modules/datasource/python-version/readme.md @@ -0,0 +1,36 @@ +This datasource returns Python releases from the [python.org API](https://www.python.org/api/v2/downloads/release/). + +It also fetches deprecated versions from the [Endoflife Date datasource](../endoflife-date/index.md). + +Because Renovate depends on [`containerbase/python-prebuild`](https://github.com/containerbase/python-prebuild/releases) it will also fetch releases from the GitHub API. + +## Example custom manager + +Below is a [custom regex manager](../../manager/regex/index.md) to update the Python versions in a Dockerfile. +Python versions sometimes drop the dot that separate the major and minor number: so `3.11` becomes `311`. +The example below handles this case. + +```dockerfile +ARG PYTHON_VERSION=311 +FROM image-python${PYTHON_VERSION}-builder:1.0.0 +``` + +```json +{ + "customManagers": [ + { + "customType": "regex", + "fileMatch": ["^Dockerfile$"], + "matchStringsStrategy": "any", + "matchStrings": [ + "ARG PYTHON_VERSION=\"?(?3(?\\d+))\"?\\s" + ], + "autoReplaceStringTemplate": "ARG PYTHON_VERSION={{{replace '\\.' '' newValue}}}\n", + "currentValueTemplate": "3.{{{minor}}}", + "datasourceTemplate": "python-version", + "versioningTemplate": "python", + "depNameTemplate": "python" + } + ] +} +``` diff --git a/lib/modules/datasource/python-version/schema.ts b/lib/modules/datasource/python-version/schema.ts new file mode 100644 index 00000000000000..2f804ef8a42f4d --- /dev/null +++ b/lib/modules/datasource/python-version/schema.ts @@ -0,0 +1,31 @@ +import { z } from 'zod'; +import type { Release } from '../types'; + +export const PythonRelease = z + .object({ + /** e.g: "Python 3.9.0b1" */ + name: z.string(), + /** e.g: "python-390b1" */ + slug: z.string(), + /** Major version e.g: 3 */ + version: z.number(), + /** is latest major version, true for Python 2.7.18 and latest Python 3 */ + is_latest: z.boolean(), + is_published: z.boolean(), + release_date: z.string(), + pre_release: z.boolean(), + release_page: z.string().nullable(), + show_on_download_page: z.boolean(), + /** Changelog e.g: "https://docs.python.org/…html#python-3-9-0-beta-1" */ + release_notes_url: z.string(), + /** Download URL e.g: "https://www.python.org/api/v2/downloads/release/436/" */ + resource_uri: z.string(), + }) + .transform( + ({ name, release_date: releaseTimestamp, pre_release }): Release => { + const version = name?.replace('Python', '').trim(); + const isStable = pre_release === false; + return { version, releaseTimestamp, isStable }; + }, + ) + .array(); diff --git a/lib/modules/datasource/ruby-version/index.ts b/lib/modules/datasource/ruby-version/index.ts index 60fae662e90a5c..7f2a2613c1d6f8 100644 --- a/lib/modules/datasource/ruby-version/index.ts +++ b/lib/modules/datasource/ruby-version/index.ts @@ -20,6 +20,13 @@ export class RubyVersionDatasource extends Datasource { override readonly defaultVersioning = rubyVersioningId; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `release-list` table in the results.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'We use the URL: https://github.com/ruby/ruby.'; + @cache({ namespace: `datasource-${RubyVersionDatasource.id}`, key: 'all' }) async getReleases({ registryUrl, diff --git a/lib/modules/datasource/rubygems/index.ts b/lib/modules/datasource/rubygems/index.ts index 58833d513b67dc..942bfd6395bc97 100644 --- a/lib/modules/datasource/rubygems/index.ts +++ b/lib/modules/datasource/rubygems/index.ts @@ -47,6 +47,13 @@ export class RubyGemsDatasource extends Datasource { private readonly versionsEndpointCache: VersionsEndpointCache; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `created_at` field in the results.'; + override readonly sourceUrlSupport = 'release'; + override readonly sourceUrlNote = + 'The source URL is determined from the `source_code_uri` field in the results.'; + @cache({ namespace: `datasource-${RubyGemsDatasource.id}`, key: ({ packageName, registryUrl }: GetReleasesConfig) => diff --git a/lib/modules/datasource/sbt-package/index.ts b/lib/modules/datasource/sbt-package/index.ts index c0b83d6c950146..b714aae786c7b6 100644 --- a/lib/modules/datasource/sbt-package/index.ts +++ b/lib/modules/datasource/sbt-package/index.ts @@ -24,6 +24,10 @@ export class SbtPackageDatasource extends MavenDatasource { override readonly registryStrategy = 'hunt'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined from the `scm` tags in the results.'; + constructor(id = SbtPackageDatasource.id) { super(id); this.http = new Http('sbt'); diff --git a/lib/modules/datasource/sbt-plugin/index.ts b/lib/modules/datasource/sbt-plugin/index.ts index 8fd574e56d227e..1d4c18a6ab039a 100644 --- a/lib/modules/datasource/sbt-plugin/index.ts +++ b/lib/modules/datasource/sbt-plugin/index.ts @@ -23,6 +23,10 @@ export class SbtPluginDatasource extends SbtPackageDatasource { override readonly defaultVersioning = ivyVersioning.id; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined from the `scm` tags in the results.'; + constructor() { super(SbtPluginDatasource.id); this.http = new Http('sbt'); diff --git a/lib/modules/datasource/terraform-module/index.ts b/lib/modules/datasource/terraform-module/index.ts index 9476cef7e5c3c3..4e3b25e4bfd3ec 100644 --- a/lib/modules/datasource/terraform-module/index.ts +++ b/lib/modules/datasource/terraform-module/index.ts @@ -2,7 +2,7 @@ import { logger } from '../../../logger'; import { cache } from '../../../util/cache/package/decorator'; import { regEx } from '../../../util/regex'; import { coerceString } from '../../../util/string'; -import { validateUrl } from '../../../util/url'; +import { isHttpUrl } from '../../../util/url'; import * as hashicorpVersioning from '../../versioning/hashicorp'; import type { GetReleasesConfig, ReleaseResult } from '../types'; import { TerraformDatasource } from './base'; @@ -25,6 +25,13 @@ export class TerraformModuleDatasource extends TerraformDatasource { override readonly defaultVersioning = hashicorpVersioning.id; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is only supported for the latest version, and is determined from the `published_at` field in the results.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined from the the `source` field in the results.'; + readonly extendedApiRegistryUrls = [ 'https://registry.terraform.io', 'https://app.terraform.io', @@ -32,7 +39,7 @@ export class TerraformModuleDatasource extends TerraformDatasource { /** * This function will fetch a package from the specified Terraform registry and return all semver versions. - * - `sourceUrl` is supported of "source" field is set + * - `sourceUrl` is supported if "source" field is set * - `homepage` is set to the Terraform registry's page if it's on the official main registry */ @cache({ @@ -162,7 +169,7 @@ export class TerraformModuleDatasource extends TerraformDatasource { }; // Add the source URL if given - if (validateUrl(res.modules[0].source)) { + if (isHttpUrl(res.modules[0].source)) { dep.sourceUrl = res.modules[0].source; } diff --git a/lib/modules/datasource/terraform-module/utils.ts b/lib/modules/datasource/terraform-module/utils.ts index e3449faa23f039..b6f981f1dc6d88 100644 --- a/lib/modules/datasource/terraform-module/utils.ts +++ b/lib/modules/datasource/terraform-module/utils.ts @@ -1,4 +1,4 @@ -import { joinUrlParts, validateUrl } from '../../../util/url'; +import { isHttpUrl, joinUrlParts } from '../../../util/url'; import type { ServiceDiscoveryEndpointType, ServiceDiscoveryResult, @@ -12,7 +12,7 @@ export function createSDBackendURL( ): string { const sdEndpoint = sdResult[sdType] ?? ''; const fullPath = joinUrlParts(sdEndpoint, subPath); - if (validateUrl(fullPath)) { + if (isHttpUrl(fullPath)) { return fullPath; } return joinUrlParts(registryURL, fullPath); diff --git a/lib/modules/datasource/terraform-provider/index.ts b/lib/modules/datasource/terraform-provider/index.ts index d52a78198b1652..56c78a7522b68b 100644 --- a/lib/modules/datasource/terraform-provider/index.ts +++ b/lib/modules/datasource/terraform-provider/index.ts @@ -42,6 +42,13 @@ export class TerraformProviderDatasource extends TerraformDatasource { override readonly registryStrategy = 'hunt'; + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is only supported for the latest version, and is determined from the `published_at` field in the results.'; + override readonly sourceUrlSupport = 'package'; + override readonly sourceUrlNote = + 'The source URL is determined from the the `source` field in the results.'; + @cache({ namespace: `datasource-${TerraformProviderDatasource.id}`, key: (getReleasesConfig: GetReleasesConfig) => diff --git a/lib/modules/datasource/types.ts b/lib/modules/datasource/types.ts index f848ffa89fba27..98f6279ebb2101 100644 --- a/lib/modules/datasource/types.ts +++ b/lib/modules/datasource/types.ts @@ -9,6 +9,8 @@ export interface GetDigestInputConfig { packageName: string; defaultRegistryUrls?: string[]; registryUrls?: string[] | null; + registryUrl?: string; + lookupName?: string; additionalRegistryUrls?: string[]; currentValue?: string; currentDigest?: string; @@ -17,6 +19,7 @@ export interface GetDigestInputConfig { export interface DigestConfig { packageName: string; + lookupName?: string; registryUrl?: string; currentValue?: string; currentDigest?: string; @@ -83,10 +86,12 @@ export interface ReleaseResult { registryUrl?: string; replacementName?: string; replacementVersion?: string; + lookupName?: string; + packageScope?: string; } export type RegistryStrategy = 'first' | 'hunt' | 'merge'; - +export type SourceUrlSupport = 'package' | 'release' | 'none'; export interface DatasourceApi extends ModuleApi { id: string; getDigest?(config: DigestConfig, newValue?: string): Promise; @@ -108,6 +113,24 @@ export interface DatasourceApi extends ModuleApi { */ customRegistrySupport: boolean; + /** + * Whether release timestamp can be returned. + */ + releaseTimestampSupport: boolean; + /** + * Notes on how release timestamp is determined. + */ + releaseTimestampNote?: string; + + /** + * Whether sourceURL can be returned. + */ + sourceUrlSupport: SourceUrlSupport; + /** + * Notes on how sourceURL is determined. + */ + sourceUrlNote?: string; + /** * Whether to perform caching in the datasource index/wrapper or not. * true: datasoure index wrapper should cache all results (based on registryUrl/packageName) diff --git a/lib/modules/datasource/unity3d/__fixtures__/beta.xml b/lib/modules/datasource/unity3d/__fixtures__/beta.xml new file mode 100644 index 00000000000000..ac53f15db16eff --- /dev/null +++ b/lib/modules/datasource/unity3d/__fixtures__/beta.xml @@ -0,0 +1,21 @@ + + + + Latest Unity Beta Releases + https://unity.com/ + Latest Unity Beta Releases + en + + + 2023.3.0b6 + https://unity.com/releases/editor/beta/2023.3.0b6 + +Beta description + + 2024-02-07T07:24:40 + Unity Technologies + 4ca2224a582d + + + + diff --git a/lib/modules/datasource/unity3d/__fixtures__/lts.xml b/lib/modules/datasource/unity3d/__fixtures__/lts.xml new file mode 100644 index 00000000000000..dc273bf6c8c232 --- /dev/null +++ b/lib/modules/datasource/unity3d/__fixtures__/lts.xml @@ -0,0 +1,21 @@ + + + + Latest Unity Lts Releases + https://unity.com/ + Latest Unity LTS Releases + en + + + 2021.3.35f1 + https://unity.com/releases/editor/whats-new/2021.3.35 + +LTS description + + 2024-02-06T15:40:15 + Unity Technologies + 157b46ce122a + + + + diff --git a/lib/modules/datasource/unity3d/__fixtures__/no_channel.xml b/lib/modules/datasource/unity3d/__fixtures__/no_channel.xml new file mode 100644 index 00000000000000..5afd7008d27baa --- /dev/null +++ b/lib/modules/datasource/unity3d/__fixtures__/no_channel.xml @@ -0,0 +1,4 @@ + + + + diff --git a/lib/modules/datasource/unity3d/__fixtures__/no_item.xml b/lib/modules/datasource/unity3d/__fixtures__/no_item.xml new file mode 100644 index 00000000000000..604cbe1c269827 --- /dev/null +++ b/lib/modules/datasource/unity3d/__fixtures__/no_item.xml @@ -0,0 +1,11 @@ + + + + Latest Unity Beta Releases + https://unity.com/ + Latest Unity Beta Releases + en + + + + diff --git a/lib/modules/datasource/unity3d/__fixtures__/no_title.xml b/lib/modules/datasource/unity3d/__fixtures__/no_title.xml new file mode 100644 index 00000000000000..35ac231d5b46fb --- /dev/null +++ b/lib/modules/datasource/unity3d/__fixtures__/no_title.xml @@ -0,0 +1,20 @@ + + + + Latest Unity Full Releases + https://unity.com/ + Latest Unity Full Releases + en + + + https://unity.com/releases/editor/whats-new/2021.3.35 + +Stable description2 + + 2024-02-06T15:40:15 + Unity Technologies + 157b46ce122a + + + + diff --git a/lib/modules/datasource/unity3d/__fixtures__/stable.xml b/lib/modules/datasource/unity3d/__fixtures__/stable.xml new file mode 100644 index 00000000000000..268e62ddec6ce4 --- /dev/null +++ b/lib/modules/datasource/unity3d/__fixtures__/stable.xml @@ -0,0 +1,31 @@ + + + + Latest Unity Full Releases + https://unity.com/ + Latest Unity Full Releases + en + + + 2023.2.9f1 + https://unity.com/releases/editor/whats-new/2023.2.9 + +Stable description + + 2024-02-07T06:56:57 + Unity Technologies + 0c9c2e1f4bef + + + 2021.3.35f1 + https://unity.com/releases/editor/whats-new/2021.3.35 + +Stable description2 + + 2024-02-06T15:40:15 + Unity Technologies + 157b46ce122a + + + + diff --git a/lib/modules/datasource/unity3d/index.spec.ts b/lib/modules/datasource/unity3d/index.spec.ts new file mode 100644 index 00000000000000..b706c72a7b92c2 --- /dev/null +++ b/lib/modules/datasource/unity3d/index.spec.ts @@ -0,0 +1,307 @@ +import { getPkgReleases } from '..'; +import { Fixtures } from '../../../../test/fixtures'; +import * as httpMock from '../../../../test/http-mock'; +import { Unity3dDatasource } from '.'; + +describe('modules/datasource/unity3d/index', () => { + const fixtures = Object.fromEntries( + [ + ...Object.keys(Unity3dDatasource.streams), + 'no_title', + 'no_channel', + 'no_item', + ].map((fixture) => [fixture, Fixtures.get(fixture + '.xml')]), + ); + + const mockRSSFeeds = (streams: { [keys: string]: string }) => { + Object.entries(streams).map(([stream, url]) => { + const content = fixtures[stream]; + + const uri = new URL(url); + + httpMock.scope(uri.origin).get(uri.pathname).reply(200, content); + }); + }; + + const stableStreamUrl = new URL(Unity3dDatasource.streams.stable); + + it('handle 500 response', async () => { + httpMock + .scope(stableStreamUrl.origin) + .get(stableStreamUrl.pathname) + .reply(500, '500'); + + const qualifyingStreams = { ...Unity3dDatasource.streams }; + delete qualifyingStreams.beta; + const response = await getPkgReleases({ + datasource: Unity3dDatasource.id, + packageName: 'm_EditorVersion', + registryUrls: [Unity3dDatasource.streams.stable], + }); + + expect(response).toBeNull(); + }); + + it('handle 200 with no XML', async () => { + httpMock + .scope(stableStreamUrl.origin) + .get(stableStreamUrl.pathname) + .reply(200, 'not xml'); + + const qualifyingStreams = { ...Unity3dDatasource.streams }; + delete qualifyingStreams.beta; + const response = await getPkgReleases({ + datasource: Unity3dDatasource.id, + packageName: 'm_EditorVersion', + registryUrls: [Unity3dDatasource.streams.stable], + }); + + expect(response).toBeNull(); + }); + + it('handles missing title element', async () => { + const content = fixtures.no_title; + httpMock + .scope(stableStreamUrl.origin) + .get(stableStreamUrl.pathname) + .reply(200, content); + + const qualifyingStreams = { ...Unity3dDatasource.streams }; + delete qualifyingStreams.beta; + const responses = await getPkgReleases({ + datasource: Unity3dDatasource.id, + packageName: 'm_EditorVersion', + registryUrls: [Unity3dDatasource.streams.stable], + }); + + expect(responses).toEqual({ + releases: [], + homepage: 'https://unity.com/', + registryUrl: Unity3dDatasource.streams.stable, + }); + }); + + it('handles missing channel element', async () => { + const content = fixtures.no_channel; + httpMock + .scope(stableStreamUrl.origin) + .get(stableStreamUrl.pathname) + .reply(200, content); + + const qualifyingStreams = { ...Unity3dDatasource.streams }; + delete qualifyingStreams.beta; + const responses = await getPkgReleases({ + datasource: Unity3dDatasource.id, + packageName: 'm_EditorVersion', + registryUrls: [Unity3dDatasource.streams.stable], + }); + + expect(responses).toEqual({ + releases: [], + homepage: 'https://unity.com/', + registryUrl: Unity3dDatasource.streams.stable, + }); + }); + + it('handles missing item element', async () => { + const content = fixtures.no_item; + httpMock + .scope(stableStreamUrl.origin) + .get(stableStreamUrl.pathname) + .reply(200, content); + + const qualifyingStreams = { ...Unity3dDatasource.streams }; + delete qualifyingStreams.beta; + const responses = await getPkgReleases({ + datasource: Unity3dDatasource.id, + packageName: 'm_EditorVersion', + registryUrls: [Unity3dDatasource.streams.stable], + }); + + expect(responses).toEqual({ + releases: [], + homepage: 'https://unity.com/', + registryUrl: Unity3dDatasource.streams.stable, + }); + }); + + it('returns beta if requested', async () => { + mockRSSFeeds({ beta: Unity3dDatasource.streams.beta }); + const responses = await getPkgReleases({ + datasource: Unity3dDatasource.id, + packageName: 'm_EditorVersion', + registryUrls: [Unity3dDatasource.streams.beta], + }); + + expect(responses).toEqual({ + registryUrl: Unity3dDatasource.streams.beta, + releases: [ + { + changelogUrl: 'https://unity.com/releases/editor/beta/2023.3.0b6', + isStable: false, + registryUrl: Unity3dDatasource.streams.beta, + releaseTimestamp: '2024-02-07T07:24:40.000Z', + version: '2023.3.0b6', + }, + ], + homepage: 'https://unity.com/', + }); + }); + + it('returns stable and lts releases by default', async () => { + const qualifyingStreams = { ...Unity3dDatasource.streams }; + delete qualifyingStreams.beta; + mockRSSFeeds(qualifyingStreams); + const responses = await getPkgReleases({ + datasource: Unity3dDatasource.id, + packageName: 'm_EditorVersion', + }); + + expect(responses).toEqual( + expect.objectContaining({ + releases: [ + { + changelogUrl: + 'https://unity.com/releases/editor/whats-new/2021.3.35', + isStable: true, + registryUrl: Unity3dDatasource.streams.stable, + releaseTimestamp: '2024-02-06T15:40:15.000Z', + version: '2021.3.35f1', + }, + { + changelogUrl: + 'https://unity.com/releases/editor/whats-new/2023.2.9', + isStable: true, + registryUrl: Unity3dDatasource.streams.stable, + releaseTimestamp: '2024-02-07T06:56:57.000Z', + version: '2023.2.9f1', + }, + ], + homepage: 'https://unity.com/', + }), + ); + + expect(responses).toEqual( + expect.objectContaining({ + releases: expect.not.arrayContaining([ + expect.objectContaining({ + version: expect.stringMatching(/\(b\)/), + registryUrl: Unity3dDatasource.streams.beta, + }), + expect.objectContaining({ + version: expect.stringMatching(/\(b\)/), + registryUrl: Unity3dDatasource.streams.beta, + }), + ]), + homepage: 'https://unity.com/', + }), + ); + }); + + it('returns hash if requested', async () => { + mockRSSFeeds({ stable: Unity3dDatasource.streams.stable }); + const responsesWithHash = await getPkgReleases({ + datasource: Unity3dDatasource.id, + packageName: 'm_EditorVersionWithRevision', + registryUrls: [Unity3dDatasource.streams.stable], + }); + + expect(responsesWithHash).toEqual( + expect.objectContaining({ + releases: expect.arrayContaining([ + expect.objectContaining({ + version: expect.stringMatching(/\(.*\)/), + }), + ]), + homepage: 'https://unity.com/', + registryUrl: Unity3dDatasource.streams.stable, + }), + ); + }); + + it('returns no hash if not requested', async () => { + mockRSSFeeds({ stable: Unity3dDatasource.streams.stable }); + const responsesWithoutHash = await getPkgReleases({ + datasource: Unity3dDatasource.id, + packageName: 'm_EditorVersion', + registryUrls: [Unity3dDatasource.streams.stable], + }); + + expect(responsesWithoutHash).toEqual( + expect.objectContaining({ + releases: expect.not.arrayContaining([ + expect.objectContaining({ + version: expect.stringMatching(/\(.*\)/), + }), + ]), + homepage: 'https://unity.com/', + registryUrl: Unity3dDatasource.streams.stable, + }), + ); + }); + + it('returns different versions for each stream', async () => { + mockRSSFeeds(Unity3dDatasource.streams); + const responses: { [keys: string]: string[] } = Object.fromEntries( + await Promise.all( + Object.keys(Unity3dDatasource.streams).map(async (stream) => [ + stream, + ( + await getPkgReleases({ + datasource: Unity3dDatasource.id, + packageName: 'm_EditorVersion', + registryUrls: [Unity3dDatasource.streams[stream]], + }) + )?.releases.map((release) => release.version), + ]), + ), + ); + + // none of the items in responses.beta are in responses.stable or responses.lts + expect( + responses.beta.every( + (betaVersion) => + !responses.stable.includes(betaVersion) && + !responses.lts.includes(betaVersion), + ), + ).toBe(true); + // some items in responses.stable are in responses.lts + expect( + responses.stable.some((stableVersion) => + responses.lts.includes(stableVersion), + ), + ).toBe(true); + // not all items in responses.stable are in responses.lts + expect( + responses.stable.every((stableVersion) => + responses.lts.includes(stableVersion), + ), + ).toBe(false); + }); + + it('returns only lts and stable by default', async () => { + const qualifyingStreams = { ...Unity3dDatasource.streams }; + delete qualifyingStreams.beta; + mockRSSFeeds(qualifyingStreams); + const responses = await getPkgReleases({ + datasource: Unity3dDatasource.id, + packageName: 'm_EditorVersionWithRevision', + }); + + expect(responses).toEqual( + expect.objectContaining({ + releases: expect.arrayContaining([ + expect.objectContaining({ + version: expect.stringMatching(/[fp]/), + registryUrl: expect.stringMatching(/(releases|lts)/), + }), + expect.objectContaining({ + version: expect.stringMatching(/[fp]/), + registryUrl: expect.stringMatching(/(releases|lts)/), + }), + ]), + homepage: 'https://unity.com/', + }), + ); + }); +}); diff --git a/lib/modules/datasource/unity3d/index.ts b/lib/modules/datasource/unity3d/index.ts new file mode 100644 index 00000000000000..24298d4a6c5e90 --- /dev/null +++ b/lib/modules/datasource/unity3d/index.ts @@ -0,0 +1,96 @@ +import { XmlDocument, XmlElement } from 'xmldoc'; +import { logger } from '../../../logger'; +import { cache } from '../../../util/cache/package/decorator'; +import * as Unity3dVersioning from '../../versioning/unity3d'; +import { Datasource } from '../datasource'; +import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; + +export class Unity3dDatasource extends Datasource { + static readonly homepage = 'https://unity.com/'; + static readonly streams: Record = { + lts: `${Unity3dDatasource.homepage}releases/editor/lts-releases.xml`, + stable: `${Unity3dDatasource.homepage}releases/editor/releases.xml`, + beta: `${Unity3dDatasource.homepage}releases/editor/beta/latest.xml`, + }; + + static readonly id = 'unity3d'; + + override readonly defaultRegistryUrls = [ + Unity3dDatasource.streams.stable, + Unity3dDatasource.streams.lts, + ]; + + override readonly defaultVersioning = Unity3dVersioning.id; + + override readonly registryStrategy = 'merge'; + + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `pubDate` tag in the results.'; + + constructor() { + super(Unity3dDatasource.id); + } + + async getByStream( + registryUrl: string | undefined, + withHash: boolean, + ): Promise { + let channel: XmlElement | undefined = undefined; + try { + const response = await this.http.get(registryUrl!); + const document = new XmlDocument(response.body); + channel = document.childNamed('channel'); + } catch (err) { + logger.error( + { err, registryUrl }, + 'Failed to request releases from Unity3d datasource', + ); + return null; + } + + if (!channel) { + return { + releases: [], + homepage: Unity3dDatasource.homepage, + registryUrl, + }; + } + const releases = channel + .childrenNamed('item') + .map((itemNode) => { + const versionWithHash = `${itemNode.childNamed('title')?.val} (${itemNode.childNamed('guid')?.val})`; + const versionWithoutHash = itemNode.childNamed('title')?.val; + const release: Release = { + version: withHash ? versionWithHash : versionWithoutHash!, + releaseTimestamp: itemNode.childNamed('pubDate')?.val, + changelogUrl: itemNode.childNamed('link')?.val, + isStable: registryUrl !== Unity3dDatasource.streams.beta, + registryUrl, + }; + return release; + }) + .filter((release) => !!release); + + return { + releases, + homepage: Unity3dDatasource.homepage, + registryUrl, + }; + } + + @cache({ + namespace: `datasource-${Unity3dDatasource.id}`, + key: ({ registryUrl, packageName }: GetReleasesConfig) => + `${registryUrl}:${packageName}`, + }) + async getReleases({ + packageName, + registryUrl, + }: GetReleasesConfig): Promise { + return await this.getByStream( + registryUrl, + packageName === 'm_EditorVersionWithRevision', + ); + } +} diff --git a/lib/modules/manager/ansible/readme.md b/lib/modules/manager/ansible/readme.md index 17d2176b176557..0182734c67eab7 100644 --- a/lib/modules/manager/ansible/readme.md +++ b/lib/modules/manager/ansible/readme.md @@ -1,3 +1,3 @@ Supports Docker-type dependency extraction from Ansible configuration files. -If you need to change the versioning format, read the [versioning](../../versioning.md) documentation to learn more. +If you need to change the versioning format, read the [versioning](../../versioning/index.md) documentation to learn more. diff --git a/lib/modules/manager/api.ts b/lib/modules/manager/api.ts index 6f618abaee49d2..66cac2fdf84bef 100644 --- a/lib/modules/manager/api.ts +++ b/lib/modules/manager/api.ts @@ -24,6 +24,7 @@ import * as conan from './conan'; import * as cpanfile from './cpanfile'; import * as crossplane from './crossplane'; import * as depsEdn from './deps-edn'; +import * as devContainer from './devcontainer'; import * as dockerCompose from './docker-compose'; import * as dockerfile from './dockerfile'; import * as droneci from './droneci'; @@ -75,6 +76,7 @@ import * as puppet from './puppet'; import * as pyenv from './pyenv'; import * as rubyVersion from './ruby-version'; import * as sbt from './sbt'; +import * as scalafmt from './scalafmt'; import * as setupCfg from './setup-cfg'; import * as swift from './swift'; import * as tekton from './tekton'; @@ -86,6 +88,7 @@ import * as tflintPlugin from './tflint-plugin'; import * as travis from './travis'; import type { ManagerApi } from './types'; import * as velaci from './velaci'; +import * as vendir from './vendir'; import * as woodpecker from './woodpecker'; const api = new Map(); @@ -117,6 +120,7 @@ api.set('conan', conan); api.set('cpanfile', cpanfile); api.set('crossplane', crossplane); api.set('deps-edn', depsEdn); +api.set('devcontainer', devContainer); api.set('docker-compose', dockerCompose); api.set('dockerfile', dockerfile); api.set('droneci', droneci); @@ -168,6 +172,7 @@ api.set('puppet', puppet); api.set('pyenv', pyenv); api.set('ruby-version', rubyVersion); api.set('sbt', sbt); +api.set('scalafmt', scalafmt); api.set('setup-cfg', setupCfg); api.set('swift', swift); api.set('tekton', tekton); @@ -178,4 +183,5 @@ api.set('terragrunt-version', terragruntVersion); api.set('tflint-plugin', tflintPlugin); api.set('travis', travis); api.set('velaci', velaci); +api.set('vendir', vendir); api.set('woodpecker', woodpecker); diff --git a/lib/modules/manager/argocd/__fixtures__/validApplication.yml b/lib/modules/manager/argocd/__fixtures__/validApplication.yml index 4533b0b64491d7..fb88a817345c5a 100644 --- a/lib/modules/manager/argocd/__fixtures__/validApplication.yml +++ b/lib/modules/manager/argocd/__fixtures__/validApplication.yml @@ -128,4 +128,13 @@ spec: helm: valueFiles: - $foo/values.yaml - +--- +{{- if .Values.enabled }} +apiVersion: argoproj.io/v1alpha1 +kind: Application +spec: + source: + chart: somechart + repoURL: https://git.example.com/foo/bar.git + targetRevision: 3.2.1 +{{- end }} diff --git a/lib/modules/manager/argocd/extract.spec.ts b/lib/modules/manager/argocd/extract.spec.ts index 0d4c70873a44af..b04165b347803a 100644 --- a/lib/modules/manager/argocd/extract.spec.ts +++ b/lib/modules/manager/argocd/extract.spec.ts @@ -165,6 +165,12 @@ spec: depName: 'somechart', registryUrls: ['https://foo.io/repo'], }, + { + currentValue: '3.2.1', + datasource: 'helm', + depName: 'somechart', + registryUrls: ['https://git.example.com/foo/bar.git'], + }, ], }); }); diff --git a/lib/modules/manager/argocd/extract.ts b/lib/modules/manager/argocd/extract.ts index 49d3daa6c9134f..a4286fe8e7f17c 100644 --- a/lib/modules/manager/argocd/extract.ts +++ b/lib/modules/manager/argocd/extract.ts @@ -6,6 +6,7 @@ import { parseYaml } from '../../../util/yaml'; import { DockerDatasource } from '../../datasource/docker'; import { GitTagsDatasource } from '../../datasource/git-tags'; import { HelmDatasource } from '../../datasource/helm'; +import { isOCIRegistry, removeOCIPrefix } from '../helmv3/oci'; import type { ExtractConfig, PackageDependency, @@ -36,6 +37,7 @@ export function extractPackageFile( definitions = parseYaml(content, null, { customSchema: ApplicationDefinition, failureBehaviour: 'filter', + removeTemplates: true, }); } catch (err) { logger.debug({ err, packageFile }, 'Failed to parse ArgoCD definition.'); @@ -51,12 +53,8 @@ function processSource(source: ApplicationSource): PackageDependency | null { // a chart variable is defined this is helm declaration if (source.chart) { // assume OCI helm chart if repoURL doesn't contain explicit protocol - if ( - source.repoURL.startsWith('oci://') || - !source.repoURL.includes('://') - ) { - let registryURL = source.repoURL.replace('oci://', ''); - registryURL = trimTrailingSlash(registryURL); + if (isOCIRegistry(source.repoURL) || !source.repoURL.includes('://')) { + const registryURL = trimTrailingSlash(removeOCIPrefix(source.repoURL)); return { depName: `${registryURL}/${source.chart}`, diff --git a/lib/modules/manager/argocd/readme.md b/lib/modules/manager/argocd/readme.md index f00c6f5bfd2447..2b86d4cdac6b10 100644 --- a/lib/modules/manager/argocd/readme.md +++ b/lib/modules/manager/argocd/readme.md @@ -2,7 +2,7 @@ To use the `argocd` manager you must set your own `fileMatch` pattern. The `argocd` manager has no default `fileMatch` pattern, because there is no common filename or directory name convention for Argo CD YAML files. By setting your own `fileMatch` Renovate avoids having to check each `*.yaml` file in a repository for a Argo CD definition. -If you need to change the versioning format, read the [versioning](../../../modules/versioning.md) documentation to learn more. +If you need to change the versioning format, read the [versioning](../../../modules/versioning/index.md) documentation to learn more. Some configuration examples: diff --git a/lib/modules/manager/asdf/extract.spec.ts b/lib/modules/manager/asdf/extract.spec.ts index ec528331a5a37e..0b168813e77f7e 100644 --- a/lib/modules/manager/asdf/extract.spec.ts +++ b/lib/modules/manager/asdf/extract.spec.ts @@ -53,6 +53,7 @@ bun 0.2.2 cargo-make 0.36.2 checkov 2.3.3 clojure 1.11.1.1182 +cosign 2.2.4 crystal 1.6.1 dart 2.19.3 deno 1.26.2 @@ -69,6 +70,7 @@ github-cli 2.32.1 gohugo extended_0.104.3 golang 1.19.2 golangci-lint 1.52.2 +gomplate 3.11.7 hadolint 2.12.0 haskell 9.4.2 helm 3.10.1 @@ -85,6 +87,7 @@ kustomize 4.5.7 lua 5.4.4 maven 3.9.6 mimirtool 2.11.0 +minikube 1.33.1 nim 1.6.8 nodejs 18.12.0 ocaml 4.14.0 @@ -96,6 +99,7 @@ poetry 1.3.2 pre-commit 3.3.1 pulumi 3.57.1 python 3.11.0 +rebar 3.23.0 ruby 3.1.2 rust 1.64.0 sbt 1.9.7 @@ -182,6 +186,13 @@ dummy 1.2.3 'regex:^(?\\d+?)\\.(?\\d+?)\\.(?\\d+)\\.(?\\d+)$', depName: 'clojure', }, + { + currentValue: '2.2.4', + datasource: 'github-releases', + packageName: 'sigstore/cosign', + depName: 'cosign', + extractVersion: '^v(?\\S+)', + }, { currentValue: '1.6.1', datasource: 'github-releases', @@ -286,6 +297,13 @@ dummy 1.2.3 depName: 'golangci-lint', extractVersion: '^v(?.+)', }, + { + currentValue: '3.11.7', + datasource: 'github-releases', + packageName: 'hairyhenderson/gomplate', + depName: 'gomplate', + extractVersion: '^v(?.+)', + }, { currentValue: '2.12.0', datasource: 'github-tags', @@ -395,6 +413,13 @@ dummy 1.2.3 depName: 'mimirtool', extractVersion: '^mimir-(?\\S+)', }, + { + currentValue: '1.33.1', + datasource: 'github-releases', + packageName: 'kubernetes/minikube', + depName: 'minikube', + extractVersion: '^v(?\\S+)', + }, { currentValue: '1.6.8', datasource: 'github-tags', @@ -468,6 +493,12 @@ dummy 1.2.3 depName: 'python', extractVersion: '^v(?\\S+)', }, + { + currentValue: '3.23.0', + datasource: 'github-tags', + packageName: 'erlang/rebar3', + depName: 'rebar', + }, { currentValue: '3.1.2', datasource: 'ruby-version', diff --git a/lib/modules/manager/asdf/upgradeable-tooling.ts b/lib/modules/manager/asdf/upgradeable-tooling.ts index 45051832b7053a..d0a1426bd012b0 100644 --- a/lib/modules/manager/asdf/upgradeable-tooling.ts +++ b/lib/modules/manager/asdf/upgradeable-tooling.ts @@ -86,7 +86,7 @@ export const upgradeableTooling: Record = { }, }, 'cargo-make': { - asdfPluginUrl: 'https://github.com/kachick/asdf-cargo-make', + asdfPluginUrl: 'https://github.com/mise-plugins/asdf-cargo-make', config: { datasource: GithubReleasesDatasource.id, packageName: 'sagiegurari/cargo-make', @@ -107,6 +107,14 @@ export const upgradeableTooling: Record = { versioning: `${regexVersioning.id}:^(?\\d+?)\\.(?\\d+?)\\.(?\\d+)\\.(?\\d+)$`, }, }, + cosign: { + asdfPluginUrl: 'https://gitlab.com/wt0f/asdf-cosign', + config: { + datasource: GithubReleasesDatasource.id, + packageName: 'sigstore/cosign', + extractVersion: '^v(?\\S+)', + }, + }, crystal: { asdfPluginUrl: 'https://github.com/asdf-community/asdf-crystal', config: { @@ -221,6 +229,14 @@ export const upgradeableTooling: Record = { extractVersion: '^v(?.+)', }, }, + gomplate: { + asdfPluginUrl: 'https://github.com/sneakybeaky/asdf-gomplate', + config: { + datasource: GithubReleasesDatasource.id, + packageName: 'hairyhenderson/gomplate', + extractVersion: '^v(?.+)', + }, + }, hadolint: { asdfPluginUrl: 'https://github.com/looztra/asdf-hadolint.git', config: { @@ -399,6 +415,14 @@ export const upgradeableTooling: Record = { extractVersion: '^mimir-(?\\S+)', }, }, + minikube: { + asdfPluginUrl: 'https://github.com/alvarobp/asdf-minikube.git', + config: { + datasource: GithubReleasesDatasource.id, + packageName: 'kubernetes/minikube', + extractVersion: '^v(?\\S+)', + }, + }, nim: { asdfPluginUrl: 'https://github.com/asdf-community/asdf-nim', config: { @@ -484,6 +508,13 @@ export const upgradeableTooling: Record = { extractVersion: '^v(?\\S+)', }, }, + rebar: { + asdfPluginUrl: 'https://github.com/Stratus3D/asdf-rebar.git', + config: { + datasource: GithubTagsDatasource.id, + packageName: 'erlang/rebar3', + }, + }, ruby: { asdfPluginUrl: 'https://github.com/asdf-vm/asdf-ruby', config: { @@ -654,7 +685,7 @@ export const upgradeableTooling: Record = { }, }, yamlfmt: { - asdfPluginUrl: 'https://github.com/kachick/asdf-yamlfmt', + asdfPluginUrl: 'https://github.com/mise-plugins/asdf-yamlfmt', config: { datasource: GithubReleasesDatasource.id, packageName: 'google/yamlfmt', diff --git a/lib/modules/manager/azure-pipelines/extract.spec.ts b/lib/modules/manager/azure-pipelines/extract.spec.ts index e53b41639e5495..eba2d1114abaca 100644 --- a/lib/modules/manager/azure-pipelines/extract.spec.ts +++ b/lib/modules/manager/azure-pipelines/extract.spec.ts @@ -35,11 +35,14 @@ describe('modules/manager/azure-pipelines/extract', () => { describe('extractRepository()', () => { it('should extract repository information', () => { expect( - extractRepository({ - type: 'github', - name: 'user/repo', - ref: 'refs/tags/v1.0.0', - }), + extractRepository( + { + type: 'github', + name: 'user/repo', + ref: 'refs/tags/v1.0.0', + }, + 'user', + ), ).toMatchObject({ depName: 'user/repo', packageName: 'https://github.com/user/repo.git', @@ -48,30 +51,39 @@ describe('modules/manager/azure-pipelines/extract', () => { it('should return null when repository type is not github', () => { expect( - extractRepository({ - type: 'bitbucket', - name: 'user/repo', - ref: 'refs/tags/v1.0.0', - }), + extractRepository( + { + type: 'bitbucket', + name: 'user/repo', + ref: 'refs/tags/v1.0.0', + }, + 'user/repo', + ), ).toBeNull(); }); it('should return null when reference is not defined specified', () => { expect( - extractRepository({ - type: 'github', - name: 'user/repo', - }), + extractRepository( + { + type: 'github', + name: 'user/repo', + }, + 'user/repo', + ), ).toBeNull(); }); it('should return null when reference is invalid tag format', () => { expect( - extractRepository({ - type: 'github', - name: 'user/repo', - ref: 'refs/head/master', - }), + extractRepository( + { + type: 'github', + name: 'user/repo', + ref: 'refs/head/master', + }, + 'user/repo', + ), ).toBeNull(); }); @@ -82,43 +94,91 @@ describe('modules/manager/azure-pipelines/extract', () => { }); expect( - extractRepository({ - type: 'git', - name: 'project/repo', - ref: 'refs/tags/v1.0.0', - }), + extractRepository( + { + type: 'git', + name: 'project/repo', + ref: 'refs/tags/v1.0.0', + }, + 'otherProject/otherRepo', + ), ).toMatchObject({ depName: 'project/repo', packageName: 'https://dev.azure.com/renovate-org/project/_git/repo', }); }); - it('should return null if repository type is git and project not in name', () => { + it('should extract Azure repository information if project is not in name but is in the config repository', () => { GlobalConfig.set({ platform: 'azure', endpoint: 'https://dev.azure.com/renovate-org', }); expect( - extractRepository({ - type: 'git', - name: 'repo', - ref: 'refs/tags/v1.0.0', - }), + extractRepository( + { + type: 'git', + name: 'repo', + ref: 'refs/tags/v1.0.0', + }, + 'project/otherrepo', + ), + ).toMatchObject({ + depName: 'project/repo', + packageName: 'https://dev.azure.com/renovate-org/project/_git/repo', + }); + }); + + it('should return null if repository type is git and project not in name nor in config repository name', () => { + GlobalConfig.set({ + platform: 'azure', + endpoint: 'https://dev.azure.com/renovate-org', + }); + + expect( + extractRepository( + { + type: 'git', + name: 'repo', + ref: 'refs/tags/v1.0.0', + }, + '', + ), + ).toBeNull(); + }); + + it('should return null if repository type is git and currentRepository is undefined', () => { + GlobalConfig.set({ + platform: 'azure', + endpoint: 'https://dev.azure.com/renovate-org', + }); + + expect( + extractRepository( + { + type: 'git', + name: 'repo', + ref: 'refs/tags/v1.0.0', + }, + undefined, + ), ).toBeNull(); }); - it('should extract return null for git repo type if platform not Azure', () => { + it('should return null for git repo type if platform not Azure', () => { GlobalConfig.set({ platform: 'github', }); expect( - extractRepository({ - type: 'git', - name: 'project/repo', - ref: 'refs/tags/v1.0.0', - }), + extractRepository( + { + type: 'git', + name: 'project/repo', + ref: 'refs/tags/v1.0.0', + }, + '', + ), ).toBeNull(); }); }); @@ -153,11 +213,15 @@ describe('modules/manager/azure-pipelines/extract', () => { describe('extractPackageFile()', () => { it('returns null for invalid azure pipelines files', () => { - expect(extractPackageFile('}', azurePipelinesFilename)).toBeNull(); + expect( + extractPackageFile('}', azurePipelinesFilename, { repository: 'repo' }), + ).toBeNull(); }); it('extracts dependencies', () => { - const res = extractPackageFile(azurePipelines, azurePipelinesFilename); + const res = extractPackageFile(azurePipelines, azurePipelinesFilename, { + repository: 'repo', + }); expect(res?.deps).toMatchObject([ { depName: 'user/repo', @@ -180,7 +244,9 @@ describe('modules/manager/azure-pipelines/extract', () => { it('should return null when there is no dependency found', () => { expect( - extractPackageFile(azurePipelinesNoDependency, azurePipelinesFilename), + extractPackageFile(azurePipelinesNoDependency, azurePipelinesFilename, { + repository: 'repo', + }), ).toBeNull(); }); @@ -196,7 +262,9 @@ describe('modules/manager/azure-pipelines/extract', () => { inputs: script: 'echo Hello World' `; - const res = extractPackageFile(packageFile, azurePipelinesFilename); + const res = extractPackageFile(packageFile, azurePipelinesFilename, { + repository: 'repo', + }); expect(res?.deps).toEqual([ { depName: 'Bash', @@ -219,7 +287,9 @@ describe('modules/manager/azure-pipelines/extract', () => { inputs: script: 'echo Hello World' `; - const res = extractPackageFile(packageFile, azurePipelinesFilename); + const res = extractPackageFile(packageFile, azurePipelinesFilename, { + repository: 'repo', + }); expect(res?.deps).toEqual([ { depName: 'Bash', @@ -242,7 +312,9 @@ describe('modules/manager/azure-pipelines/extract', () => { inputs: script: 'echo Hello World' `; - const res = extractPackageFile(packageFile, azurePipelinesFilename); + const res = extractPackageFile(packageFile, azurePipelinesFilename, { + repository: 'repo', + }); expect(res?.deps).toEqual([ { depName: 'Bash', @@ -264,7 +336,9 @@ describe('modules/manager/azure-pipelines/extract', () => { inputs: script: 'echo Hello World' `; - const res = extractPackageFile(packageFile, azurePipelinesFilename); + const res = extractPackageFile(packageFile, azurePipelinesFilename, { + repository: 'repo', + }); expect(res?.deps).toEqual([ { depName: 'Bash', @@ -286,7 +360,9 @@ describe('modules/manager/azure-pipelines/extract', () => { inputs: script: 'echo Hello World' `; - const res = extractPackageFile(packageFile, azurePipelinesFilename); + const res = extractPackageFile(packageFile, azurePipelinesFilename, { + repository: 'repo', + }); expect(res?.deps).toEqual([ { depName: 'Bash', @@ -308,7 +384,9 @@ describe('modules/manager/azure-pipelines/extract', () => { inputs: script: 'echo Hello World' `; - const res = extractPackageFile(packageFile, azurePipelinesFilename); + const res = extractPackageFile(packageFile, azurePipelinesFilename, { + repository: 'repo', + }); expect(res?.deps).toEqual([ { depName: 'Bash', @@ -330,7 +408,9 @@ describe('modules/manager/azure-pipelines/extract', () => { inputs: script: 'echo Hello World' `; - const res = extractPackageFile(packageFile, azurePipelinesFilename); + const res = extractPackageFile(packageFile, azurePipelinesFilename, { + repository: 'repo', + }); expect(res?.deps).toEqual([ { depName: 'Bash', @@ -352,7 +432,9 @@ describe('modules/manager/azure-pipelines/extract', () => { inputs: script: 'echo Hello World' `; - const res = extractPackageFile(packageFile, azurePipelinesFilename); + const res = extractPackageFile(packageFile, azurePipelinesFilename, { + repository: 'repo', + }); expect(res?.deps).toEqual([ { depName: 'Bash', @@ -373,7 +455,9 @@ describe('modules/manager/azure-pipelines/extract', () => { inputs: script: 'echo Hello World' `; - const res = extractPackageFile(packageFile, azurePipelinesFilename); + const res = extractPackageFile(packageFile, azurePipelinesFilename, { + repository: 'repo', + }); expect(res?.deps).toEqual([ { depName: 'Bash', @@ -392,7 +476,9 @@ describe('modules/manager/azure-pipelines/extract', () => { inputs: script: 'echo Hello World' `; - const res = extractPackageFile(packageFile, azurePipelinesFilename); + const res = extractPackageFile(packageFile, azurePipelinesFilename, { + repository: 'repo', + }); expect(res?.deps).toEqual([ { depName: 'Bash', @@ -409,7 +495,9 @@ describe('modules/manager/azure-pipelines/extract', () => { inputs: script: 'echo Hello World' `; - const res = extractPackageFile(packageFile, azurePipelinesFilename); + const res = extractPackageFile(packageFile, azurePipelinesFilename, { + repository: 'repo', + }); expect(res?.deps).toEqual([ { depName: 'Bash', @@ -424,7 +512,9 @@ describe('modules/manager/azure-pipelines/extract', () => { steps: - bash: 'echo Hello World'; `; - const res = extractPackageFile(packageFile, azurePipelinesFilename); + const res = extractPackageFile(packageFile, azurePipelinesFilename, { + repository: 'repo', + }); expect(res).toBeNull(); }); }); diff --git a/lib/modules/manager/azure-pipelines/extract.ts b/lib/modules/manager/azure-pipelines/extract.ts index 433d4b7c2f987f..7a814f0100f3f3 100644 --- a/lib/modules/manager/azure-pipelines/extract.ts +++ b/lib/modules/manager/azure-pipelines/extract.ts @@ -6,7 +6,11 @@ import { joinUrlParts } from '../../../util/url'; import { AzurePipelinesTasksDatasource } from '../../datasource/azure-pipelines-tasks'; import { GitTagsDatasource } from '../../datasource/git-tags'; import { getDep } from '../dockerfile/extract'; -import type { PackageDependency, PackageFileContent } from '../types'; +import type { + ExtractConfig, + PackageDependency, + PackageFileContent, +} from '../types'; import { AzurePipelines, AzurePipelinesYaml, @@ -23,22 +27,20 @@ const AzurePipelinesTaskRegex = regEx(/^(?[^@]+)@(?.*)$/); export function extractRepository( repository: Repository, + currentRepository?: string, ): PackageDependency | null { let repositoryUrl = null; + let depName = repository.name; + if (repository.type === 'github') { repositoryUrl = `https://github.com/${repository.name}.git`; } else if (repository.type === 'git') { - // "git" type indicates an AzureDevOps repository. - // The repository URL is only deducible if we are running on AzureDevOps (so can use the endpoint) - // and the name is of the form `Project/Repository`. - // The name could just be the repository name, in which case AzureDevOps defaults to the - // same project, which is not currently accessible here. It could be deduced later by exposing - // the repository URL to managers. - // https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/resources-repositories-repository?view=azure-pipelines#types const platform = GlobalConfig.get('platform'); const endpoint = GlobalConfig.get('endpoint'); + if (platform === 'azure' && endpoint) { + // extract the project name if the repository from which the pipline is referencing templates contains the Azure DevOps project name if (repository.name.includes('/')) { const [projectName, repoName] = repository.name.split('/'); repositoryUrl = joinUrlParts( @@ -47,9 +49,20 @@ export function extractRepository( '_git', encodeURIComponent(repoName), ); + + // if the repository from which the pipline is referencing templates does not contain the Azure DevOps project name, get the project name from the repository containing the pipeline file being process + } else if (currentRepository?.includes('/')) { + const projectName = currentRepository.split('/')[0]; + depName = `${projectName}/${repository.name}`; + repositoryUrl = joinUrlParts( + endpoint, + encodeURIComponent(projectName), + '_git', + encodeURIComponent(repository.name), + ); } else { logger.debug( - 'Renovate cannot update repositories that do not include the project name', + 'Renovate cannot update Azure pipelines in git repositories when neither the current repository nor the target repository contains the Azure DevOps project name.', ); } } @@ -67,7 +80,7 @@ export function extractRepository( autoReplaceStringTemplate: 'refs/tags/{{newValue}}', currentValue: repository.ref.replace('refs/tags/', ''), datasource: GitTagsDatasource.id, - depName: repository.name, + depName, depType: 'gitTags', packageName: repositoryUrl, replaceString: repository.ref, @@ -168,6 +181,7 @@ function extractJobs(jobs: Jobs | undefined): PackageDependency[] { export function extractPackageFile( content: string, packageFile: string, + config: ExtractConfig, ): PackageFileContent | null { logger.trace(`azurePipelines.extractPackageFile(${packageFile})`); const deps: PackageDependency[] = []; @@ -178,7 +192,7 @@ export function extractPackageFile( } for (const repository of coerceArray(pkg.resources?.repositories)) { - const dep = extractRepository(repository); + const dep = extractRepository(repository, config.repository); if (dep) { deps.push(dep); } diff --git a/lib/modules/manager/azure-pipelines/readme.md b/lib/modules/manager/azure-pipelines/readme.md index aee00bea3e63ae..3536e8181f2527 100644 --- a/lib/modules/manager/azure-pipelines/readme.md +++ b/lib/modules/manager/azure-pipelines/readme.md @@ -42,7 +42,7 @@ resources: ref: refs/tags/v0.5.1 containers: - container: linux - image: ubuntu:16.04 + image: ubuntu:24.04 - container: python image: python:3.7@sha256:3870d35b962a943df72d948580fc66ceaaee1c4fbd205930f32e0f0760eb1077 diff --git a/lib/modules/manager/batect/readme.md b/lib/modules/manager/batect/readme.md index 6c16968630fe09..5ba4e364834c6b 100644 --- a/lib/modules/manager/batect/readme.md +++ b/lib/modules/manager/batect/readme.md @@ -28,6 +28,6 @@ For example: ### Bundle versioning -This manager assumes that any bundles referenced use tags for versioning, and that these tags use [SemVer](../../versioning.md#semantic-versioning). +This manager assumes that any bundles referenced use tags for versioning, and that these tags use [SemVer](../../versioning/semver/index.md). The implementation of SemVer is strict - versions must follow the `X.Y.Z` or `vX.Y.Z` format. Versions that don't match this format (eg. `X.Y`) will be ignored. diff --git a/lib/modules/manager/bazel/index.ts b/lib/modules/manager/bazel/index.ts index 77b156c93b3469..5b12b8aafe65cf 100644 --- a/lib/modules/manager/bazel/index.ts +++ b/lib/modules/manager/bazel/index.ts @@ -9,7 +9,7 @@ import { extractPackageFile } from './extract'; export { extractPackageFile, updateArtifacts }; export const defaultConfig = { - fileMatch: ['(^|/)WORKSPACE(|\\.bazel)$', '\\.bzl$'], + fileMatch: ['(^|/)WORKSPACE(|\\.bazel|\\.bzlmod)$', '\\.bzl$'], }; export const categories: Category[] = ['bazel']; diff --git a/lib/modules/manager/bazel/readme.md b/lib/modules/manager/bazel/readme.md index 9f00e3f1d78476..4c36ae61b0bd4e 100644 --- a/lib/modules/manager/bazel/readme.md +++ b/lib/modules/manager/bazel/readme.md @@ -2,4 +2,4 @@ Bazel is quite unlike most other "package managers" that Renovate supports, whic Instead, Bazel is a build tool so supports a multitude of languages/datasources. Renovate does not support all possible Bazel references, although would like to, and feature requests are welcome. -If you need to change the versioning format, read the [versioning](../../versioning.md) documentation to learn more. +If you need to change the versioning format, read the [versioning](../../versioning/index.md) documentation to learn more. diff --git a/lib/modules/manager/bitbucket-pipelines/readme.md b/lib/modules/manager/bitbucket-pipelines/readme.md index dbd5f08fc60c13..99e3c571924277 100644 --- a/lib/modules/manager/bitbucket-pipelines/readme.md +++ b/lib/modules/manager/bitbucket-pipelines/readme.md @@ -1,3 +1,3 @@ Extracts dependencies from Bitbucket Pipelines config files. -If you need to change the versioning format, read the [versioning](../../versioning.md) documentation to learn more. +If you need to change the versioning format, read the [versioning](../../versioning/index.md) documentation to learn more. diff --git a/lib/modules/manager/buildkite/readme.md b/lib/modules/manager/buildkite/readme.md index 50b917a34d5c81..23ba19fe73ef6c 100644 --- a/lib/modules/manager/buildkite/readme.md +++ b/lib/modules/manager/buildkite/readme.md @@ -1,3 +1,3 @@ Used for updating Docker dependencies in Buildkite configuration files. -If you need to change the versioning format, read the [versioning](../../versioning.md) documentation to learn more. +If you need to change the versioning format, read the [versioning](../../versioning/index.md) documentation to learn more. diff --git a/lib/modules/manager/bundler/__fixtures__/Gemfile.sourceBlockWithGroups b/lib/modules/manager/bundler/__fixtures__/Gemfile.sourceBlockWithGroups new file mode 100644 index 00000000000000..2b0dc5ec239fd4 --- /dev/null +++ b/lib/modules/manager/bundler/__fixtures__/Gemfile.sourceBlockWithGroups @@ -0,0 +1,12 @@ +source 'https://hub.tech.my.domain.de/artifactory/api/gems/my-gems-prod-local/' do + gem 'sfn_my_dep1', "~> 1" + gem 'sfn_my_dep2', "~> 1" + + group :test, :development do + gem 'internal_test_gem', "~> 1" + end + + group :production do + gem 'internal_production_gem', "~> 1" + end +end diff --git a/lib/modules/manager/bundler/extract.spec.ts b/lib/modules/manager/bundler/extract.spec.ts index fe7916baeebcf1..cbee5d033f7c3e 100644 --- a/lib/modules/manager/bundler/extract.spec.ts +++ b/lib/modules/manager/bundler/extract.spec.ts @@ -26,6 +26,9 @@ const sourceBlockWithNewLinesGemfileLock = Fixtures.get( const sourceBlockWithNewLinesGemfile = Fixtures.get( 'Gemfile.sourceBlockWithNewLines', ); +const sourceBlockWithGroupsGemfile = Fixtures.get( + 'Gemfile.sourceBlockWithGroups', +); describe('modules/manager/bundler/extract', () => { describe('extractPackageFile()', () => { @@ -124,4 +127,18 @@ describe('modules/manager/bundler/extract', () => { expect(res).toMatchSnapshot(); expect(res?.deps).toHaveLength(2); }); + + it('parses source blocks with groups in Gemfile', async () => { + fs.readLocalFile.mockResolvedValueOnce(sourceBlockWithGroupsGemfile); + const res = await extractPackageFile( + sourceBlockWithGroupsGemfile, + 'Gemfile', + ); + expect(res?.deps).toMatchObject([ + { depName: 'internal_test_gem', currentValue: '"~> 1"' }, + { depName: 'internal_production_gem', currentValue: '"~> 1"' }, + { depName: 'sfn_my_dep1', currentValue: '"~> 1"' }, + { depName: 'sfn_my_dep2', currentValue: '"~> 1"' }, + ]); + }); }); diff --git a/lib/modules/manager/bundler/extract.ts b/lib/modules/manager/bundler/extract.ts index 167dcaeccb9600..b204056b5db61f 100644 --- a/lib/modules/manager/bundler/extract.ts +++ b/lib/modules/manager/bundler/extract.ts @@ -16,12 +16,70 @@ export async function extractPackageFile( content: string, packageFile?: string, ): Promise { + let lineNumber: number; + async function processGroupBlock( + line: string, + repositoryUrl?: string, + trimGroupLine: boolean = false, + ): Promise { + const groupMatch = regEx(/^group\s+(.*?)\s+do/).exec(line); + if (groupMatch) { + const depTypes = groupMatch[1] + .split(',') + .map((group) => group.trim()) + .map((group) => group.replace(regEx(/^:/), '')); + + const groupLineNumber = lineNumber; + let groupContent = ''; + let groupLine = ''; + + while ( + lineNumber < lines.length && + (trimGroupLine ? groupLine.trim() !== 'end' : groupLine !== 'end') + ) { + lineNumber += 1; + groupLine = lines[lineNumber]; + + // istanbul ignore if + if (!is.string(groupLine)) { + logger.debug( + { content, packageFile, type: 'groupLine' }, + 'Bundler parsing error', + ); + groupLine = 'end'; + } + if (trimGroupLine ? groupLine.trim() !== 'end' : groupLine !== 'end') { + groupContent += formatContent(groupLine); + } + } + + const groupRes = await extractPackageFile(groupContent); + if (groupRes) { + res.deps = res.deps.concat( + groupRes.deps.map((dep) => { + const depObject = { + ...dep, + depTypes, + managerData: { + lineNumber: + Number(dep.managerData?.lineNumber) + groupLineNumber + 1, + }, + }; + if (repositoryUrl) { + depObject.registryUrls = [repositoryUrl]; + } + return depObject; + }), + ); + } + } + } const res: PackageFileContent = { registryUrls: [], deps: [], }; const lines = content.split(newlineRegex); - for (let lineNumber = 0; lineNumber < lines.length; lineNumber += 1) { + for (lineNumber = 0; lineNumber < lines.length; lineNumber += 1) { const line = lines[lineNumber]; let sourceMatch: RegExpMatchArray | null = null; for (const delimiter of delimiters) { @@ -61,44 +119,9 @@ export async function extractPackageFile( dep.datasource = RubyGemsDatasource.id; res.deps.push(dep); } - const groupMatch = regEx(/^group\s+(.*?)\s+do/).exec(line); - if (groupMatch) { - const depTypes = groupMatch[1] - .split(',') - .map((group) => group.trim()) - .map((group) => group.replace(regEx(/^:/), '')); - const groupLineNumber = lineNumber; - let groupContent = ''; - let groupLine = ''; - while (lineNumber < lines.length && groupLine !== 'end') { - lineNumber += 1; - groupLine = lines[lineNumber]; - // istanbul ignore if - if (!is.string(groupLine)) { - logger.debug( - { content, packageFile, type: 'groupLine' }, - 'Bundler parsing error', - ); - groupLine = 'end'; - } - if (groupLine !== 'end') { - groupContent += formatContent(groupLine); - } - } - const groupRes = await extractPackageFile(groupContent); - if (groupRes) { - res.deps = res.deps.concat( - groupRes.deps.map((dep) => ({ - ...dep, - depTypes, - managerData: { - lineNumber: - Number(dep.managerData?.lineNumber) + groupLineNumber + 1, - }, - })), - ); - } - } + + await processGroupBlock(line); + for (const delimiter of delimiters) { const sourceBlockMatch = regEx( `^source\\s+${delimiter}(.*?)${delimiter}\\s+do`, @@ -108,6 +131,7 @@ export async function extractPackageFile( const sourceLineNumber = lineNumber; let sourceContent = ''; let sourceLine = ''; + while (lineNumber < lines.length && sourceLine.trim() !== 'end') { lineNumber += 1; sourceLine = lines[lineNumber]; @@ -119,11 +143,16 @@ export async function extractPackageFile( ); sourceLine = 'end'; } - if (sourceLine !== 'end') { + + await processGroupBlock(sourceLine.trim(), repositoryUrl, true); + + if (sourceLine.trim() !== 'end') { sourceContent += formatContent(sourceLine); } } + const sourceRes = await extractPackageFile(sourceContent); + if (sourceRes) { res.deps = res.deps.concat( sourceRes.deps.map((dep) => ({ diff --git a/lib/modules/manager/bundler/update-locked.spec.ts b/lib/modules/manager/bundler/update-locked.spec.ts index 37caaadac05207..a0566ef66b1765 100644 --- a/lib/modules/manager/bundler/update-locked.spec.ts +++ b/lib/modules/manager/bundler/update-locked.spec.ts @@ -53,7 +53,7 @@ describe('modules/manager/bundler/update-locked', () => { expect(updateLockedDependency(config).status).toBe('unsupported'); }); - it('returns update-falied incase of errors', () => { + it('returns update-failed in case of errors', () => { const config: UpdateLockedConfig = { packageFile: 'Gemfile', lockFile: 'Gemfile.lock', diff --git a/lib/modules/manager/cargo/__fixtures__/Cargo.7.toml b/lib/modules/manager/cargo/__fixtures__/Cargo.7.toml new file mode 100644 index 00000000000000..aa2274f4a8aabf --- /dev/null +++ b/lib/modules/manager/cargo/__fixtures__/Cargo.7.toml @@ -0,0 +1,9 @@ +[package] +name = "renovate-test" +version = "0.1.0" +authors = ["John Doe "] +edition = "2018" + +[dependencies] +tokio = "0.2" + diff --git a/lib/modules/manager/cargo/extract.spec.ts b/lib/modules/manager/cargo/extract.spec.ts index cf80672e7cf76c..16d8c7b2b04ff2 100644 --- a/lib/modules/manager/cargo/extract.spec.ts +++ b/lib/modules/manager/cargo/extract.spec.ts @@ -23,6 +23,7 @@ const cargo4toml = Fixtures.get('Cargo.4.toml'); const cargo5toml = Fixtures.get('Cargo.5.toml'); const cargo6configtoml = Fixtures.get('cargo.6.config.toml'); const cargo6toml = Fixtures.get('Cargo.6.toml'); +const cargo7toml = Fixtures.get('Cargo.7.toml'); const lockfileUpdateCargotoml = Fixtures.get('lockfile-update/Cargo.toml'); @@ -167,6 +168,31 @@ replace-with = "private-crates"`, ]); }); + it('extracts overridden source registry indexes from .cargo/config.toml', async () => { + mockReadLocalFile({ + '.cargo/config.toml': codeBlock`[source.crates-io-replacement] +registry = "https://github.com/replacement/testregistry" + +[source.crates-io] +replace-with = "crates-io-replacement"`, + }); + const res = await extractPackageFile(cargo7toml, 'Cargo.toml', { + ...config, + }); + expect(res?.deps).toEqual([ + { + currentValue: '0.2', + datasource: 'crate', + depName: 'tokio', + depType: 'dependencies', + managerData: { + nestedVersion: false, + }, + registryUrls: ['https://github.com/replacement/testregistry'], + }, + ]); + }); + it('extracts registries overridden to the default', async () => { mockReadLocalFile({ '.cargo/config.toml': codeBlock`[source.mcorbin] diff --git a/lib/modules/manager/cargo/extract.ts b/lib/modules/manager/cargo/extract.ts index cd0d3f746fbe4e..76ea1bdd2de440 100644 --- a/lib/modules/manager/cargo/extract.ts +++ b/lib/modules/manager/cargo/extract.ts @@ -191,6 +191,14 @@ function resolveRegistryIndex( ); } + const sourceRegistry = config.source?.[registryName]?.registry; + if (sourceRegistry) { + logger.debug( + `Replacing cargo source registry with ${sourceRegistry} for ${registryName}`, + ); + return sourceRegistry; + } + const registryIndex = config.registries?.[registryName]?.index; if (registryIndex) { return registryIndex; diff --git a/lib/modules/manager/cargo/range.spec.ts b/lib/modules/manager/cargo/range.spec.ts index 62c07b1389b2b3..d41d3e6cad3571 100644 --- a/lib/modules/manager/cargo/range.spec.ts +++ b/lib/modules/manager/cargo/range.spec.ts @@ -15,11 +15,11 @@ describe('modules/manager/cargo/range', () => { expect(getRangeStrategy(config)).toBe('widen'); }); - it('defaults to bump', () => { + it('defaults to update-lockfile', () => { const config: RangeConfig = { rangeStrategy: 'auto', currentValue: '1.0.0', }; - expect(getRangeStrategy(config)).toBe('bump'); + expect(getRangeStrategy(config)).toBe('update-lockfile'); }); }); diff --git a/lib/modules/manager/cargo/range.ts b/lib/modules/manager/cargo/range.ts index a04628d55c127d..fbfe99da61a6f7 100644 --- a/lib/modules/manager/cargo/range.ts +++ b/lib/modules/manager/cargo/range.ts @@ -11,5 +11,5 @@ export function getRangeStrategy({ if (currentValue?.includes('<')) { return 'widen'; } - return 'bump'; + return 'update-lockfile'; } diff --git a/lib/modules/manager/cargo/readme.md b/lib/modules/manager/cargo/readme.md index 46195044aebb2a..94baa4638720b7 100644 --- a/lib/modules/manager/cargo/readme.md +++ b/lib/modules/manager/cargo/readme.md @@ -3,7 +3,9 @@ Extracts dependencies from `Cargo.toml` files, and also updates `Cargo.lock` fil When using the default rangeStrategy=auto: - If a "less than" instruction is found (e.g. `<2`) then `rangeStrategy=widen` will be selected, -- Otherwise, `rangeStrategy=bump` will be selected. +- Otherwise, `rangeStrategy=update-lockfile` will be selected. + +The `update-lockfile` default means that most upgrades will update `Cargo.lock` files without the need to change the value in `Cargo.toml`. ### Private Modules Authentication diff --git a/lib/modules/manager/cargo/types.ts b/lib/modules/manager/cargo/types.ts index 68bfa4740432a8..b28c3ced50fa27 100644 --- a/lib/modules/manager/cargo/types.ts +++ b/lib/modules/manager/cargo/types.ts @@ -45,6 +45,7 @@ export interface CargoRegistry { export interface CargoSource { 'replace-with'?: string; + registry?: string; } /** diff --git a/lib/modules/manager/cargo/update-locked.spec.ts b/lib/modules/manager/cargo/update-locked.spec.ts index 5c99da7acdaf0a..c3ba15d26b6a6b 100644 --- a/lib/modules/manager/cargo/update-locked.spec.ts +++ b/lib/modules/manager/cargo/update-locked.spec.ts @@ -53,7 +53,7 @@ describe('modules/manager/cargo/update-locked', () => { expect(updateLockedDependency(config).status).toBe('unsupported'); }); - it('returns update-failed incase of errors', () => { + it('returns update-failed in case of errors', () => { const config: UpdateLockedConfig = { packageFile: 'Cargo.toml', lockFile: 'Cargo.lock', diff --git a/lib/modules/manager/circleci/__fixtures__/config2.yml b/lib/modules/manager/circleci/__fixtures__/config2.yml index cf55b2aebc4d87..3f885f3e5c0a15 100644 --- a/lib/modules/manager/circleci/__fixtures__/config2.yml +++ b/lib/modules/manager/circleci/__fixtures__/config2.yml @@ -5,11 +5,11 @@ orbs: release-workflows: hutson/library-release-workflows@4.1.0 # Comments help me understand my work. # The next line is intentionally just whitespace! - + no-version: abc/def # Comments help me understand my work. - volatile: zzz/zzz@volatile + volatile: zzz/zzz@volatile # Comments help me understand my work. test_plan: &test_plan steps: diff --git a/lib/modules/manager/circleci/extract.ts b/lib/modules/manager/circleci/extract.ts index fe1e6adf445e22..9d971de87724c0 100644 --- a/lib/modules/manager/circleci/extract.ts +++ b/lib/modules/manager/circleci/extract.ts @@ -29,7 +29,9 @@ export function extractPackageFile( lineNumber += 1; continue; } - const orbMatch = regEx(/^\s+([^:]+):\s(.+)$/).exec(orbLine); + const orbMatch = regEx(/^\s+([^:]+):\s(.+?)(?:\s*#.*)?$/).exec( + orbLine, + ); if (orbMatch) { logger.trace('orbMatch'); foundOrbOrNoop = true; diff --git a/lib/modules/manager/circleci/index.spec.ts b/lib/modules/manager/circleci/index.spec.ts new file mode 100644 index 00000000000000..527ffb6aa50e78 --- /dev/null +++ b/lib/modules/manager/circleci/index.spec.ts @@ -0,0 +1,24 @@ +import { regexMatches } from '../../../../test/util'; +import { defaultConfig } from '.'; + +describe('modules/manager/circleci/index', () => { + describe('file names match fileMatch', () => { + it.each` + path | expected + ${'.circleci/config.yml'} | ${true} + ${'.circleci/config.yaml'} | ${true} + ${'.circleci/foo.yaml'} | ${true} + ${'.circleci/foo.yml'} | ${true} + ${'.circleci/foo/config.yaml'} | ${true} + ${'.circleci/foo/bar.yml'} | ${true} + ${'foo/.circleci/bar.yaml'} | ${true} + ${'foo.yml'} | ${false} + ${'circleci/foo.yml'} | ${false} + ${'circleci/foo.yml'} | ${false} + ${'.circleci_foo/bar.yml'} | ${false} + ${'.circleci/foo.toml'} | ${false} + `('regexMatches("$path") === $expected', ({ path, expected }) => { + expect(regexMatches(path, defaultConfig.fileMatch)).toBe(expected); + }); + }); +}); diff --git a/lib/modules/manager/circleci/index.ts b/lib/modules/manager/circleci/index.ts index 3c2fb9b0532b23..353e21627b8795 100644 --- a/lib/modules/manager/circleci/index.ts +++ b/lib/modules/manager/circleci/index.ts @@ -10,7 +10,7 @@ export const displayName = 'CircleCI'; export const url = 'https://circleci.com/docs/configuration-reference'; export const defaultConfig = { - fileMatch: ['(^|/)\\.circleci/config\\.ya?ml$'], + fileMatch: ['(^|/)\\.circleci/.+\\.ya?ml$'], }; export const categories: Category[] = ['ci']; diff --git a/lib/modules/manager/circleci/readme.md b/lib/modules/manager/circleci/readme.md index 64fb2a71f02b44..2380045fd9daba 100644 --- a/lib/modules/manager/circleci/readme.md +++ b/lib/modules/manager/circleci/readme.md @@ -1,6 +1,6 @@ The `circleci` manager extracts both `docker` as well as `orb` datasources from CircleCI config files. -If you need to change the versioning format, read the [versioning](../../versioning.md) documentation to learn more. +If you need to change the versioning format, read the [versioning](../../versioning/index.md) documentation to learn more. ### Private orbs diff --git a/lib/modules/manager/cloudbuild/readme.md b/lib/modules/manager/cloudbuild/readme.md index ed5bcd87c3499b..c8fb219f32159d 100644 --- a/lib/modules/manager/cloudbuild/readme.md +++ b/lib/modules/manager/cloudbuild/readme.md @@ -1,3 +1,3 @@ The `cloudbuild` manager extracts `docker` datasources from [Cloud Build config files](https://cloud.google.com/build/docs/configuring-builds/create-basic-configuration). -If you need to change the versioning format, read the [versioning](../../versioning.md) documentation to learn more. +If you need to change the versioning format, read the [versioning](../../versioning/index.md) documentation to learn more. diff --git a/lib/modules/manager/composer/utils.ts b/lib/modules/manager/composer/utils.ts index 353f2b7d0365c2..89328f611ef24d 100644 --- a/lib/modules/manager/composer/utils.ts +++ b/lib/modules/manager/composer/utils.ts @@ -2,7 +2,7 @@ import { quote } from 'shlex'; import { GlobalConfig } from '../../../config/global'; import { logger } from '../../../logger'; -import type { HostRuleSearchResult } from '../../../types'; +import type { CombinedHostRule } from '../../../types'; import type { ToolConstraint } from '../../../util/exec/types'; import { coerceNumber } from '../../../util/number'; import { api, id as composerVersioningId } from '../../versioning/composer'; @@ -113,6 +113,6 @@ export function extractConstraints( return res; } -export function isArtifactAuthEnabled(rule: HostRuleSearchResult): boolean { +export function isArtifactAuthEnabled(rule: CombinedHostRule): boolean { return !rule.artifactAuth || rule.artifactAuth.includes('composer'); } diff --git a/lib/modules/manager/crossplane/readme.md b/lib/modules/manager/crossplane/readme.md index 68b661596ef16b..f8b983a20a4f96 100644 --- a/lib/modules/manager/crossplane/readme.md +++ b/lib/modules/manager/crossplane/readme.md @@ -10,7 +10,7 @@ The `crossplane` manager supports these `depType`s: You can use these `depType`'s to control which dependencies Renovate will upgrade. -If you need to change the versioning format, read the [versioning](../../../modules/versioning.md) documentation to learn more. +If you need to change the versioning format, read the [versioning](../../../modules/versioning/index.md) documentation to learn more. Some configuration examples: diff --git a/lib/modules/manager/custom/regex/__snapshots__/index.spec.ts.snap b/lib/modules/manager/custom/regex/__snapshots__/index.spec.ts.snap index 5132942cd4da4f..25dd3663151b9f 100644 --- a/lib/modules/manager/custom/regex/__snapshots__/index.spec.ts.snap +++ b/lib/modules/manager/custom/regex/__snapshots__/index.spec.ts.snap @@ -198,7 +198,7 @@ exports[`modules/manager/custom/regex/index extracts registryUrl 1`] = ` { "currentValue": "8.12.13", "datasource": "helm", - "depName": "prometheus-operator", + "packageName": "prometheus-operator", "registryUrls": [ "https://charts.helm.sh/stable", ], @@ -212,7 +212,7 @@ exports[`modules/manager/custom/regex/index extracts registryUrl 1`] = ` "matchStrings": [ "chart: *repository: (?.*?) - *name: (?.*?) + *name: (?.*?) *version: (?.*) ", ], diff --git a/lib/modules/manager/custom/regex/index.spec.ts b/lib/modules/manager/custom/regex/index.spec.ts index 1859c097011c87..671883e1c6d820 100644 --- a/lib/modules/manager/custom/regex/index.spec.ts +++ b/lib/modules/manager/custom/regex/index.spec.ts @@ -48,7 +48,7 @@ describe('modules/manager/custom/regex/index', () => { it('returns null if no dependencies found', async () => { const config = { matchStrings: [ - 'ENV .*?_VERSION=(?.*) # (?.*?)/(?[^&]*?)(\\&versioning=(?[^&]*?))?\\s', + 'ENV .*?_VERSION=(?.*) # (?.*?)/(?[^&]*?)(\\&versioning=(?[^&]*?))?\\s', ], versioningTemplate: '{{#if versioning}}{{versioning}}{{else}}semver{{/if}}', @@ -95,7 +95,7 @@ describe('modules/manager/custom/regex/index', () => { it('extracts registryUrl', async () => { const config = { matchStrings: [ - 'chart:\n *repository: (?.*?)\n *name: (?.*?)\n *version: (?.*)\n', + 'chart:\n *repository: (?.*?)\n *name: (?.*?)\n *version: (?.*)\n', ], datasourceTemplate: 'helm', }; @@ -121,7 +121,7 @@ describe('modules/manager/custom/regex/index', () => { { currentValue: '8.12.13', datasource: 'helm', - depName: 'prometheus-operator', + packageName: 'prometheus-operator', registryUrls: ['https://charts.helm.sh/stable'], }, ], @@ -351,7 +351,8 @@ describe('modules/manager/custom/regex/index', () => { }); it('extracts with combination strategy: sets replaceString when current version group present', async () => { - const config = { + const config: CustomExtractConfig = { + matchStringsStrategy: 'combination', matchStrings: [ 'image:\\s+(?[a-z-]+)(?::(?[a-z0-9.-]+))?(?:@(?sha256:[a-f0-9]+))?', ], @@ -377,7 +378,8 @@ describe('modules/manager/custom/regex/index', () => { }); it('extracts with combination strategy: sets replaceString when current digest group present', async () => { - const config = { + const config: CustomExtractConfig = { + matchStringsStrategy: 'combination', matchStrings: [ 'image:\\s+(?[a-z-]+)(?::(?[a-z0-9.-]+))?(?:@(?sha256:[a-f0-9]+))?', ], diff --git a/lib/modules/manager/custom/regex/readme.md b/lib/modules/manager/custom/regex/readme.md index 9d13ab364db097..add7b7d1142aff 100644 --- a/lib/modules/manager/custom/regex/readme.md +++ b/lib/modules/manager/custom/regex/readme.md @@ -24,13 +24,12 @@ Before Renovate can look up a dependency and decide about updates, it needs this - The dependency's name - Which `datasource` to use: npm, Docker, GitHub tags, and so on. For how to format this references see [datasource overview](../../datasource/index.md#supported-datasources) -- Which version scheme to use: defaults to `semver-coerced`, but you may set another value like `pep440`. Supported versioning schemes can be found in the [versioning overview](../../versioning.md#supported-versioning) +- Which version scheme to use: defaults to `semver-coerced`, but you may set another value like `pep440`. Supported versioning schemes can be found in the [versioning overview](../../versioning/index.md#supported-versioning) Configuration-wise, it works like this: - You must capture the `currentValue` of the dependency in a named capture group -- You must have either a `depName` capture group or a `depNameTemplate` config field -- You can optionally have a `packageName` capture group or a `packageNameTemplate` if it differs from `depName` +- You must have either a `depName` or `packageName` capture group, or use on of the respective template fields ( `depNameTemplate` and `packageNameTemplate` ) - You must have either a `datasource` capture group or a `datasourceTemplate` config field - You can optionally have a `depType` capture group or a `depTypeTemplate` config field - You can optionally have a `versioning` capture group or a `versioningTemplate` config field. If neither are present, Renovate will use `semver-coerced` as the default @@ -88,15 +87,13 @@ But you don't want to write a regex custom manager rule for _each_ variable. Instead you enhance your `Dockerfile` like this: ```Dockerfile -ARG IMAGE=node:12@sha256:6e5264cd4cfaefd7174b2bc10c7f9a1c2b99d98d127fc57a802d264da9fb43bd -FROM ${IMAGE} - # renovate: datasource=github-tags depName=nodejs/node versioning=node -ENV NODE_VERSION=10.19.0 - # renovate: datasource=github-releases depName=composer/composer +# renovate: datasource=github-tags depName=node packageName=nodejs/node versioning=node +ENV NODE_VERSION=20.10.0 +# renovate: datasource=github-releases depName=composer packageName=composer/composer ENV COMPOSER_VERSION=1.9.3 -# renovate: datasource=docker depName=docker versioning=docker +# renovate: datasource=docker packageName=docker versioning=docker ENV DOCKER_VERSION=19.03.1 -# renovate: datasource=npm depName=yarn +# renovate: datasource=npm packageName=yarn ENV YARN_VERSION=1.19.1 ``` @@ -109,18 +106,11 @@ You could configure Renovate to update the `Dockerfile` like this: "customManagers": [ { "customType": "regex", - "fileMatch": ["^Dockerfile$"], + "description": "Update _VERSION variables in Dockerfiles", + "fileMatch": ["(^|/|\\.)Dockerfile$", "(^|/)Dockerfile\\.[^/]*$"], "matchStrings": [ - "datasource=(?.*?) depName=(?.*?)( versioning=(?.*?))?\\sENV .*?_VERSION=(?.*)\\s" - ], - "versioningTemplate": "{{#if versioning}}{{{versioning}}}{{else}}semver{{/if}}" - }, - { - "fileMatch": ["^Dockerfile$"], - "matchStrings": [ - "ARG IMAGE=(?.*?):(?.*?)@(?sha256:[a-f0-9]+)\\s" - ], - "datasourceTemplate": "docker" + "# renovate: datasource=(?[a-z-]+?)(?: depName=(?.+?))? packageName=(?.+?)(?: versioning=(?[a-z-]+?))?\\s(?:ENV|ARG) .+?_VERSION=(?.+?)\\s" + ] } ] } diff --git a/lib/modules/manager/custom/regex/utils.ts b/lib/modules/manager/custom/regex/utils.ts index 48a0dbfe82a7c3..b1ddc709b89d01 100644 --- a/lib/modules/manager/custom/regex/utils.ts +++ b/lib/modules/manager/custom/regex/utils.ts @@ -124,10 +124,12 @@ export function isValidDependency({ depName, currentValue, currentDigest, + packageName, }: PackageDependency): boolean { // check if all the fields are set return ( - is.nonEmptyStringAndNotWhitespace(depName) && + (is.nonEmptyStringAndNotWhitespace(depName) || + is.nonEmptyStringAndNotWhitespace(packageName)) && (is.nonEmptyStringAndNotWhitespace(currentDigest) || is.nonEmptyStringAndNotWhitespace(currentValue)) ); diff --git a/lib/modules/manager/devcontainer/extract.spec.ts b/lib/modules/manager/devcontainer/extract.spec.ts new file mode 100644 index 00000000000000..76b5b28993f898 --- /dev/null +++ b/lib/modules/manager/devcontainer/extract.spec.ts @@ -0,0 +1,358 @@ +import { codeBlock } from 'common-tags'; +import { extractPackageFile } from '.'; + +describe('modules/manager/devcontainer/extract', () => { + describe('extractPackageFile()', () => { + it('returns null when the dev container JSON file is empty', () => { + // Arrange + const content = ''; + const packageFile = ''; + const extractConfig = {}; + // Act + const result = extractPackageFile(content, packageFile, extractConfig); + + // Assert + expect(result).toBeNull(); + }); + + it('returns null when the dev container JSON file contents are malformed', () => { + // Arrange + const content = 'malformed json}}}'; + const packageFile = ''; + const extractConfig = {}; + // Act + const result = extractPackageFile(content, packageFile, extractConfig); + + // Assert + expect(result).toBeNull(); + }); + + it('tests if JSONC can be parsed', () => { + // Arrange + const content = codeBlock(` + { + // hello + "features": { + "devcontainer.registry.renovate.com/test/features/first:1.2.3": {} + } + }`); + const extractConfig = {}; + // Act + const result = extractPackageFile( + content, + 'devcontainer.json', + extractConfig, + ); + + // Assert + expect(result).toEqual({ + deps: [ + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentDigest: undefined, + currentValue: '1.2.3', + datasource: 'docker', + depName: 'devcontainer.registry.renovate.com/test/features/first', + depType: 'feature', + pinDigests: false, + replaceString: + 'devcontainer.registry.renovate.com/test/features/first:1.2.3', + }, + ], + }); + }); + + it('returns feature image deps when only the features property is defined in dev container JSON file', () => { + // Arrange + const content = codeBlock(` + { + "features": { + "devcontainer.registry.renovate.com/test/features/first:1.2.3": {}, + "devcontainer.registry.renovate.com/test/features/second:4.5.6": {} + } + }`); + const extractConfig = {}; + // Act + const result = extractPackageFile( + content, + 'devcontainer.json', + extractConfig, + ); + + // Assert + expect(result).toEqual({ + deps: [ + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentDigest: undefined, + currentValue: '1.2.3', + datasource: 'docker', + depName: 'devcontainer.registry.renovate.com/test/features/first', + depType: 'feature', + pinDigests: false, + replaceString: + 'devcontainer.registry.renovate.com/test/features/first:1.2.3', + }, + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentDigest: undefined, + currentValue: '4.5.6', + datasource: 'docker', + depName: 'devcontainer.registry.renovate.com/test/features/second', + depType: 'feature', + pinDigests: false, + replaceString: + 'devcontainer.registry.renovate.com/test/features/second:4.5.6', + }, + ], + }); + }); + + it('returns image and feature image deps when both image and features properties are defined in dev container JSON file', () => { + // Arrange + const content = codeBlock(` + { + "image": "devcontainer.registry.renovate.com/test/image:1.2.3", + "features": { + "devcontainer.registry.renovate.com/test/feature:4.5.6": {} + } + }`); + const extractConfig = {}; + + // Act + const result = extractPackageFile( + content, + 'devcontainer.json', + extractConfig, + ); + + // Assert + expect(result).toEqual({ + deps: [ + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentDigest: undefined, + currentValue: '1.2.3', + datasource: 'docker', + depName: 'devcontainer.registry.renovate.com/test/image', + depType: 'image', + replaceString: + 'devcontainer.registry.renovate.com/test/image:1.2.3', + }, + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentDigest: undefined, + currentValue: '4.5.6', + datasource: 'docker', + depName: 'devcontainer.registry.renovate.com/test/feature', + depType: 'feature', + pinDigests: false, + replaceString: + 'devcontainer.registry.renovate.com/test/feature:4.5.6', + }, + ], + }); + }); + + it('returns image dep when only the image property is defined in dev container JSON file', () => { + // Arrange + const content = codeBlock(` + { + "image": "devcontainer.registry.renovate.com/test/image:1.2.3" + }`); + const extractConfig = {}; + // Act + const result = extractPackageFile( + content, + 'devcontainer.json', + extractConfig, + ); + + // Assert + expect(result).toEqual({ + deps: [ + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentDigest: undefined, + currentValue: '1.2.3', + datasource: 'docker', + depName: 'devcontainer.registry.renovate.com/test/image', + depType: 'image', + replaceString: + 'devcontainer.registry.renovate.com/test/image:1.2.3', + }, + ], + }); + }); + + it('returns null when the only feature property is malformed and no image property is defined in dev container JSON file', () => { + // Arrange + const content = codeBlock(` + { + "features": { + "malformedFeature": {} + } + }`); + const extractConfig = {}; + // Act + const result = extractPackageFile( + content, + 'devcontainer.json', + extractConfig, + ); + + // Assert + expect(result).toBeNull(); + }); + + it('returns null when the features property is malformed and no image property is defined in dev container JSON file', () => { + // Arrange + const content = codeBlock(` + { + "features": "devcontainer.registry.renovate.com/test:1.2.3" + }`); + const extractConfig = {}; + // Act + const result = extractPackageFile( + content, + 'devcontainer.json', + extractConfig, + ); + + // Assert + expect(result).toBeNull(); + }); + + it('returns null when the image property is malformed and no features are defined in dev container JSON file', () => { + // Arrange + const content = codeBlock(` + { + "image:": "devcontainer.registry.renovate.com/test/image:1.2.3" + }`); + const extractConfig = {}; + // Act + const result = extractPackageFile( + content, + 'devcontainer.json', + extractConfig, + ); + + // Assert + expect(result).toBeNull(); + }); + + it('returns null when no image or features properties are defined in dev container JSON file', () => { + // Arrange + const content = codeBlock('{}'); + const extractConfig = {}; + // Act + const result = extractPackageFile( + content, + 'devcontainer.json', + extractConfig, + ); + + // Assert + expect(result).toBeNull(); + }); + + it('returns null when the features property is null and no image property is defined in dev container JSON file', () => { + // Arrange + const content = codeBlock(` + { + "features": null + }`); + const extractConfig = {}; + // Act + const result = extractPackageFile( + content, + 'devcontainer.json', + extractConfig, + ); + + // Assert + expect(result).toBeNull(); + }); + + it('returns null when the features property is not defined and the image property is null in dev container JSON file', () => { + // Arrange + const content = codeBlock(` + { + "image": null + }`); + const extractConfig = {}; + // Act + const result = extractPackageFile( + content, + 'devcontainer.json', + extractConfig, + ); + + // Assert + expect(result).toBeNull(); + }); + + it('returns null when both the image and features properties are null', () => { + // Arrange + const content = codeBlock(` + { + "image": null, + "features": null + }`); + const extractConfig = {}; + // Act + const result = extractPackageFile( + content, + 'devcontainer.json', + extractConfig, + ); + + // Assert + expect(result).toBeNull(); + }); + + it('returns only docker dependencies when non-docker feature types are defined beneath the features property in dev container JSON file', () => { + // Arrange + const content = codeBlock(` + { + "features": { + "devcontainer.registry.renovate.com/test/feature:1.2.3": {}, + "./localfeature": {}, + "devcontainer.registry.renovate.com/test/feature/other.tgz": {} + } + }`); + const extractConfig = {}; + + // Act + const result = extractPackageFile( + content, + 'devcontainer.json', + extractConfig, + ); + + // Assert + expect(result).toEqual({ + deps: [ + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentDigest: undefined, + currentValue: '1.2.3', + datasource: 'docker', + depName: 'devcontainer.registry.renovate.com/test/feature', + depType: 'feature', + pinDigests: false, + replaceString: + 'devcontainer.registry.renovate.com/test/feature:1.2.3', + }, + ], + }); + }); + }); +}); diff --git a/lib/modules/manager/devcontainer/extract.ts b/lib/modules/manager/devcontainer/extract.ts new file mode 100644 index 00000000000000..489563507a8214 --- /dev/null +++ b/lib/modules/manager/devcontainer/extract.ts @@ -0,0 +1,90 @@ +import { logger } from '../../../logger'; +import { isValidDependency } from '../custom/regex/utils'; +import { getDep as getDockerDep } from '../dockerfile/extract'; +import type { + ExtractConfig, + PackageDependency, + PackageFileContent, +} from '../types'; +import { DevContainerFile } from './schema'; + +export function extractPackageFile( + content: string, + packageFile: string, + extractConfig: ExtractConfig, +): PackageFileContent | null { + try { + const file = DevContainerFile.parse(content); + const deps: PackageDependency[] = []; + + const image = file?.image ?? null; + const imageDep = getDep(image, packageFile, extractConfig.registryAliases); + + if (imageDep) { + imageDep.depType = 'image'; + deps.push(imageDep); + } else { + logger.trace( + { packageFile }, + 'No image defined in dev container JSON file.', + ); + } + + const features = file.features; + + if (features) { + for (const feature of Object.keys(features)) { + const featureDep = getDep( + feature, + packageFile, + extractConfig.registryAliases, + ); + if (featureDep) { + featureDep.depType = 'feature'; + featureDep.pinDigests = false; + deps.push(featureDep); + continue; + } + logger.trace( + { feature, packageFile }, + 'Skipping invalid dependency in dev container JSON file.', + ); + } + } + + if (deps.length < 1) { + logger.trace( + { packageFile }, + 'No dependencies to process for dev container JSON file.', + ); + return null; + } + + return { deps }; + } catch (err) { + logger.debug( + { err, packageFile }, + 'Error extracting dev container JSON file.', + ); + return null; + } +} + +function getDep( + subject: string | null, + packageFile: string, + registryAliases?: Record, +): PackageDependency | null { + if (!subject) { + return null; + } + const dep = getDockerDep(subject, true, registryAliases); + if (!isValidDependency(dep)) { + logger.trace( + { subject, packageFile }, + 'Skipping invalid docker dependency in dev container JSON file.', + ); + return null; + } + return dep; +} diff --git a/lib/modules/manager/devcontainer/index.ts b/lib/modules/manager/devcontainer/index.ts new file mode 100644 index 00000000000000..d73e88efcce8d1 --- /dev/null +++ b/lib/modules/manager/devcontainer/index.ts @@ -0,0 +1,11 @@ +import type { Category } from '../../../constants'; +import { DockerDatasource } from '../../datasource/docker'; +export { extractPackageFile } from './extract'; + +export const defaultConfig = { + fileMatch: ['^.devcontainer/devcontainer.json$', '^.devcontainer.json$'], +}; + +export const categories: Category[] = ['docker']; + +export const supportedDatasources = [DockerDatasource.id]; diff --git a/lib/modules/manager/devcontainer/readme.md b/lib/modules/manager/devcontainer/readme.md new file mode 100644 index 00000000000000..2e709d7e747241 --- /dev/null +++ b/lib/modules/manager/devcontainer/readme.md @@ -0,0 +1,6 @@ +Extracts all Docker images from the `image` and `features` properties in these files: + +- `.devcontainer.json` +- `.devcontainer/devcontainer.json` + +The `devcontainer` manager does _not_ support `build.dockerFile` or `dockerComposeFile` values as these are covered by the [`dockerfile`](../dockerfile/index.md) and [`docker-compose`](../docker-compose/index.md) managers respectively. diff --git a/lib/modules/manager/devcontainer/schema.ts b/lib/modules/manager/devcontainer/schema.ts new file mode 100644 index 00000000000000..38498632044b3e --- /dev/null +++ b/lib/modules/manager/devcontainer/schema.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; +import { Jsonc } from '../../../util/schema-utils'; + +export const DevContainerFile = Jsonc.pipe( + z.object({ + image: z.string().optional(), + features: z.record(z.unknown()).optional(), + }), +); + +export type DevContainerFile = z.infer; diff --git a/lib/modules/manager/docker-compose/readme.md b/lib/modules/manager/docker-compose/readme.md index b2c04f3c3944f3..fd60918cd96df3 100644 --- a/lib/modules/manager/docker-compose/readme.md +++ b/lib/modules/manager/docker-compose/readme.md @@ -1,3 +1,3 @@ Extracts all Docker images from with Docker Compose YAML files. -If you need to change the versioning format, read the [versioning](../../versioning.md) documentation to learn more. +If you need to change the versioning format, read the [versioning](../../versioning/index.md) documentation to learn more. diff --git a/lib/modules/manager/dockerfile/extract.spec.ts b/lib/modules/manager/dockerfile/extract.spec.ts index 787afe2cd3f972..5ea952477dae83 100644 --- a/lib/modules/manager/dockerfile/extract.spec.ts +++ b/lib/modules/manager/dockerfile/extract.spec.ts @@ -669,6 +669,27 @@ describe('modules/manager/dockerfile/extract', () => { ]); }); + it('handles debian with prefixes and registries', () => { + const res = extractPackageFile( + 'FROM docker.io/library/debian:10\n', + '', + {}, + )?.deps; + expect(res).toEqual([ + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentDigest: undefined, + currentValue: '10', + datasource: 'docker', + depName: 'docker.io/library/debian', + depType: 'final', + replaceString: 'docker.io/library/debian:10', + versioning: 'debian', + }, + ]); + }); + it('handles prefixes', () => { const res = extractPackageFile('FROM amd64/ubuntu:18.04\n', '', {})?.deps; expect(res).toEqual([ @@ -687,6 +708,27 @@ describe('modules/manager/dockerfile/extract', () => { ]); }); + it('handles prefixes with registries', () => { + const res = extractPackageFile( + 'FROM public.ecr.aws/ubuntu/ubuntu:18.04\n', + '', + {}, + )?.deps; + expect(res).toEqual([ + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentDigest: undefined, + currentValue: '18.04', + datasource: 'docker', + depName: 'public.ecr.aws/ubuntu/ubuntu', + depType: 'final', + replaceString: 'public.ecr.aws/ubuntu/ubuntu:18.04', + versioning: 'ubuntu', + }, + ]); + }); + it('handles implausible line continuation', () => { const res = extractPackageFile( 'FROM alpine:3.5\n\nRUN something \\', diff --git a/lib/modules/manager/dockerfile/extract.ts b/lib/modules/manager/dockerfile/extract.ts index fdce4db0522315..81e2da71bf4327 100644 --- a/lib/modules/manager/dockerfile/extract.ts +++ b/lib/modules/manager/dockerfile/extract.ts @@ -214,12 +214,12 @@ export function getDep( } } - if (dep.depName === 'ubuntu') { + if (dep.depName === 'ubuntu' || dep.depName?.endsWith('/ubuntu')) { dep.versioning = ubuntuVersioning.id; } if ( - dep.depName === 'debian' && + (dep.depName === 'debian' || dep.depName?.endsWith('/debian')) && debianVersioning.api.isVersion(dep.currentValue) ) { dep.versioning = debianVersioning.id; diff --git a/lib/modules/manager/dockerfile/readme.md b/lib/modules/manager/dockerfile/readme.md index 4a46c995490f56..c9f1ba919a5fb9 100644 --- a/lib/modules/manager/dockerfile/readme.md +++ b/lib/modules/manager/dockerfile/readme.md @@ -19,4 +19,4 @@ For example, if you know that an image follows SemVer, you can tell Renovate to } ``` -Read [Renovate's Docker Versioning](../../versioning.md#docker-versioning) docs to learn more. +Read [Renovate's Docker Versioning](../../versioning/docker/index.md) docs to learn more. diff --git a/lib/modules/manager/droneci/readme.md b/lib/modules/manager/droneci/readme.md index bfb40cafdefd3b..f15e69b6e75bf7 100644 --- a/lib/modules/manager/droneci/readme.md +++ b/lib/modules/manager/droneci/readme.md @@ -1,3 +1,3 @@ Extracts Docker-type dependencies from DroneCI config files. -If you need to change the versioning format, read the [versioning](../../versioning.md) documentation to learn more. +If you need to change the versioning format, read the [versioning](../../versioning/index.md) documentation to learn more. diff --git a/lib/modules/manager/fleet/__fixtures__/valid_fleet.yaml b/lib/modules/manager/fleet/__fixtures__/valid_fleet.yaml index 88126be690b2ed..3f48f82f6be4ba 100644 --- a/lib/modules/manager/fleet/__fixtures__/valid_fleet.yaml +++ b/lib/modules/manager/fleet/__fixtures__/valid_fleet.yaml @@ -14,4 +14,14 @@ helm: releaseName: logging-operator version: 3.17.7 values: - +--- +defaultNamespace: monitoring +helm: + repo: https://prometheus-community.github.io/helm-charts + chart: prometheus + version: 25.19.1 +--- +defaultNamespace: external-dns +helm: + chart: oci://registry-1.docker.io/bitnamicharts/external-dns + version: 7.1.2 diff --git a/lib/modules/manager/fleet/extract.spec.ts b/lib/modules/manager/fleet/extract.spec.ts index 70229b856e1744..100cada8ee219a 100644 --- a/lib/modules/manager/fleet/extract.spec.ts +++ b/lib/modules/manager/fleet/extract.spec.ts @@ -61,6 +61,23 @@ kind: Fleet registryUrls: ['https://kubernetes-charts.banzaicloud.com'], depType: 'fleet', }, + { + currentValue: '25.19.1', + datasource: 'helm', + depName: 'prometheus', + registryUrls: [ + 'https://prometheus-community.github.io/helm-charts', + ], + depType: 'fleet', + }, + { + currentValue: '7.1.2', + datasource: 'docker', + depName: 'registry-1.docker.io/bitnamicharts/external-dns', + packageName: 'registry-1.docker.io/bitnamicharts/external-dns', + depType: 'fleet', + pinDigests: false, + }, ]); }); diff --git a/lib/modules/manager/fleet/extract.ts b/lib/modules/manager/fleet/extract.ts index 32c4c705c336bb..6e409394ae4ffe 100644 --- a/lib/modules/manager/fleet/extract.ts +++ b/lib/modules/manager/fleet/extract.ts @@ -4,6 +4,8 @@ import { regEx } from '../../../util/regex'; import { parseYaml } from '../../../util/yaml'; import { GitTagsDatasource } from '../../datasource/git-tags'; import { HelmDatasource } from '../../datasource/helm'; +import { getDep } from '../dockerfile/extract'; +import { isOCIRegistry, removeOCIPrefix } from '../helmv3/oci'; import { checkIfStringIsPath } from '../terraform/util'; import type { PackageDependency, PackageFileContent } from '../types'; import { FleetFile, type FleetHelmBlock, GitRepo } from './schema'; @@ -50,6 +52,24 @@ function extractFleetHelmBlock(doc: FleetHelmBlock): PackageDependency { skipReason: 'missing-depname', }; } + + if (isOCIRegistry(doc.chart)) { + const dockerDep = getDep( + `${removeOCIPrefix(doc.chart)}:${doc.version}`, + false, + ); + + return { + ...dockerDep, + depType: 'fleet', + depName: dockerDep.depName, + packageName: dockerDep.depName, + // https://github.com/helm/helm/issues/10312 + // https://github.com/helm/helm/issues/10678 + pinDigests: false, + }; + } + dep.depName = doc.chart; dep.packageName = doc.chart; diff --git a/lib/modules/manager/fleet/index.ts b/lib/modules/manager/fleet/index.ts index e6368fc46df7c0..a1e6f118d55cb0 100644 --- a/lib/modules/manager/fleet/index.ts +++ b/lib/modules/manager/fleet/index.ts @@ -1,4 +1,5 @@ import type { Category } from '../../../constants'; +import { DockerDatasource } from '../../datasource/docker'; import { GitTagsDatasource } from '../../datasource/git-tags'; import { HelmDatasource } from '../../datasource/helm'; @@ -12,4 +13,8 @@ export const defaultConfig = { export const categories: Category[] = ['cd', 'kubernetes']; -export const supportedDatasources = [GitTagsDatasource.id, HelmDatasource.id]; +export const supportedDatasources = [ + GitTagsDatasource.id, + HelmDatasource.id, + DockerDatasource.id, +]; diff --git a/lib/modules/manager/fleet/schema.ts b/lib/modules/manager/fleet/schema.ts index 08d3d4342a450b..fffc392e2236cf 100644 --- a/lib/modules/manager/fleet/schema.ts +++ b/lib/modules/manager/fleet/schema.ts @@ -4,13 +4,10 @@ const FleetHelmBlock = z.object({ chart: z.string().optional(), repo: z.string().optional(), version: z.string().optional(), + releaseName: z.string().optional(), }); export type FleetHelmBlock = z.infer; -const FleetFileHelm = FleetHelmBlock.extend({ - releaseName: z.string(), -}); - /** Represent a GitRepo Kubernetes manifest of Fleet. @link https://fleet.rancher.io/gitrepo-add/#create-gitrepo-instance @@ -32,7 +29,7 @@ export type GitRepo = z.infer; @link https://fleet.rancher.io/gitrepo-structure/#fleetyaml */ export const FleetFile = z.object({ - helm: FleetFileHelm, + helm: FleetHelmBlock, targetCustomizations: z .array( z.object({ diff --git a/lib/modules/manager/flux/extract.spec.ts b/lib/modules/manager/flux/extract.spec.ts index 6c4b5c4f1192dd..f754ec6dc5981d 100644 --- a/lib/modules/manager/flux/extract.spec.ts +++ b/lib/modules/manager/flux/extract.spec.ts @@ -502,6 +502,9 @@ describe('modules/manager/flux/extract', () => { expect(result).toEqual({ deps: [ { + currentDigest: undefined, + currentValue: undefined, + datasource: 'docker', depName: 'ghcr.io/kyverno/manifests/kyverno', skipReason: 'unversioned-reference', }, @@ -523,6 +526,11 @@ describe('modules/manager/flux/extract', () => { url: oci://ghcr.io/kyverno/manifests/kyverno `, 'test.yaml', + { + registryAliases: { + 'ghcr.io': 'ghcr.proxy.test/some/path', + }, + }, ); expect(result).toEqual({ deps: [ @@ -531,7 +539,7 @@ describe('modules/manager/flux/extract', () => { '{{#if newValue}}{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', currentValue: 'v1.8.2', currentDigest: undefined, - depName: 'ghcr.io/kyverno/manifests/kyverno', + depName: 'ghcr.proxy.test/some/path/kyverno/manifests/kyverno', datasource: DockerDatasource.id, replaceString: 'v1.8.2', }, @@ -723,10 +731,16 @@ describe('modules/manager/flux/extract', () => { }); it('should handle HelmRepository with type OCI', async () => { - const result = await extractAllPackageFiles(config, [ - 'lib/modules/manager/flux/__fixtures__/helmOCISource.yaml', - 'lib/modules/manager/flux/__fixtures__/helmOCIRelease.yaml', - ]); + const result = await extractAllPackageFiles( + { + ...config, + registryAliases: { 'ghcr.io': 'ghcr.proxy.test/some/path' }, + }, + [ + 'lib/modules/manager/flux/__fixtures__/helmOCISource.yaml', + 'lib/modules/manager/flux/__fixtures__/helmOCIRelease.yaml', + ], + ); expect(result).toEqual([ { deps: [ @@ -735,7 +749,7 @@ describe('modules/manager/flux/extract', () => { datasource: DockerDatasource.id, depName: 'actions-runner-controller-charts/gha-runner-scale-set', packageName: - 'ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set', + 'ghcr.proxy.test/some/path/actions/actions-runner-controller-charts/gha-runner-scale-set', }, ], packageFile: diff --git a/lib/modules/manager/flux/extract.ts b/lib/modules/manager/flux/extract.ts index 7e4fd03ec58d00..1bf9258c57036e 100644 --- a/lib/modules/manager/flux/extract.ts +++ b/lib/modules/manager/flux/extract.ts @@ -12,6 +12,7 @@ import { GithubTagsDatasource } from '../../datasource/github-tags'; import { GitlabTagsDatasource } from '../../datasource/gitlab-tags'; import { HelmDatasource } from '../../datasource/helm'; import { getDep } from '../dockerfile/extract'; +import { isOCIRegistry, removeOCIPrefix } from '../helmv3/oci'; import type { ExtractConfig, PackageDependency, @@ -124,6 +125,7 @@ function resolveSystemManifest( function resolveResourceManifest( manifest: ResourceFluxManifest, helmRepositories: HelmRepository[], + registryAliases: Record | undefined, ): PackageDependency[] { const deps: PackageDependency[] = []; for (const resource of manifest.resources) { @@ -146,16 +148,17 @@ function resolveResourceManifest( if (matchingRepositories.length) { dep.registryUrls = matchingRepositories .map((repo) => { - if ( - repo.spec.type === 'oci' || - repo.spec.url.startsWith('oci://') - ) { + if (repo.spec.type === 'oci' || isOCIRegistry(repo.spec.url)) { // Change datasource to Docker dep.datasource = DockerDatasource.id; // Ensure the URL is a valid OCI path - dep.packageName = `${repo.spec.url.replace('oci://', '')}/${ - resource.spec.chart.spec.chart - }`; + dep.packageName = getDep( + `${removeOCIPrefix(repo.spec.url)}/${ + resource.spec.chart.spec.chart + }`, + false, + registryAliases, + ).depName; return null; } else { return repo.spec.url; @@ -197,17 +200,23 @@ function resolveResourceManifest( break; } case 'OCIRepository': { - const container = resource.spec.url?.replace('oci://', ''); - let dep: PackageDependency = { - depName: container, - }; + const container = removeOCIPrefix(resource.spec.url); + let dep = getDep(container, false, registryAliases); if (resource.spec.ref?.digest) { - dep = getDep(`${container}@${resource.spec.ref.digest}`, false); + dep = getDep( + `${container}@${resource.spec.ref.digest}`, + false, + registryAliases, + ); if (resource.spec.ref?.tag) { logger.debug('A digest and tag was found, ignoring tag'); } } else if (resource.spec.ref?.tag) { - dep = getDep(`${container}:${resource.spec.ref.tag}`, false); + dep = getDep( + `${container}:${resource.spec.ref.tag}`, + false, + registryAliases, + ); dep.autoReplaceStringTemplate = '{{#if newValue}}{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}'; dep.replaceString = resource.spec.ref.tag; @@ -225,6 +234,7 @@ function resolveResourceManifest( export function extractPackageFile( content: string, packageFile: string, + config?: ExtractConfig, ): PackageFileContent | null { const manifest = readManifest(content, packageFile); if (!manifest) { @@ -244,7 +254,11 @@ export function extractPackageFile( deps = resolveSystemManifest(manifest); break; case 'resource': { - deps = resolveResourceManifest(manifest, helmRepositories); + deps = resolveResourceManifest( + manifest, + helmRepositories, + config?.registryAliases, + ); break; } } @@ -252,7 +266,7 @@ export function extractPackageFile( } export async function extractAllPackageFiles( - _config: ExtractConfig, + config: ExtractConfig, packageFiles: string[], ): Promise[] | null> { const manifests: FluxManifest[] = []; @@ -285,7 +299,11 @@ export async function extractAllPackageFiles( deps = resolveSystemManifest(manifest); break; case 'resource': { - deps = resolveResourceManifest(manifest, helmRepositories); + deps = resolveResourceManifest( + manifest, + helmRepositories, + config.registryAliases, + ); break; } } diff --git a/lib/modules/manager/flux/readme.md b/lib/modules/manager/flux/readme.md index 854cfe5e104036..5e5e5f6b3b7da8 100644 --- a/lib/modules/manager/flux/readme.md +++ b/lib/modules/manager/flux/readme.md @@ -80,4 +80,4 @@ If instead you have all your Flux manifests inside a `flux/` directory, you woul ### Versioning -If you need to change the versioning format, read the [versioning](../../versioning.md) documentation to learn more. +If you need to change the versioning format, read the [versioning](../../versioning/index.md) documentation to learn more. diff --git a/lib/modules/manager/fvm/extract.spec.ts b/lib/modules/manager/fvm/extract.spec.ts index 2211d90eb7c14a..010a3e2e4e0dea 100644 --- a/lib/modules/manager/fvm/extract.spec.ts +++ b/lib/modules/manager/fvm/extract.spec.ts @@ -23,7 +23,7 @@ describe('modules/manager/fvm/extract', () => { ).toBeNull(); }); - it('returns a result', () => { + it('returns a result for .fvm/fvm_config.json', () => { const res = extractPackageFile( '{"flutterSdkVersion": "2.10.1", "flavors": {}}', packageFile, @@ -38,7 +38,19 @@ describe('modules/manager/fvm/extract', () => { ]); }); - it('supports non range', () => { + it('returns a result for .fvmrc', () => { + const res = extractPackageFile('{"flutter": "2.10.1"}', packageFile); + expect(res?.deps).toEqual([ + { + currentValue: '2.10.1', + datasource: 'flutter-version', + depName: 'flutter', + packageName: 'flutter/flutter', + }, + ]); + }); + + it('supports non range for .fvm/fvm_config.json', () => { const res = extractPackageFile( '{"flutterSdkVersion": "stable", "flavors": {}}', packageFile, @@ -52,5 +64,17 @@ describe('modules/manager/fvm/extract', () => { }, ]); }); + + it('supports non range for .fvmrc', () => { + const res = extractPackageFile('{"flutter": "stable"}', packageFile); + expect(res?.deps).toEqual([ + { + currentValue: 'stable', + datasource: 'flutter-version', + depName: 'flutter', + packageName: 'flutter/flutter', + }, + ]); + }); }); }); diff --git a/lib/modules/manager/fvm/extract.ts b/lib/modules/manager/fvm/extract.ts index eb050391f85d46..ac617db010bb15 100644 --- a/lib/modules/manager/fvm/extract.ts +++ b/lib/modules/manager/fvm/extract.ts @@ -1,38 +1,33 @@ -import is from '@sindresorhus/is'; import { logger } from '../../../logger'; +import { Json } from '../../../util/schema-utils'; import { FlutterVersionDatasource } from '../../datasource/flutter-version'; import type { PackageDependency, PackageFileContent } from '../types'; - -interface FvmConfig { - flutterSdkVersion: string; -} +import { FvmConfig } from './schema'; export function extractPackageFile( content: string, packageFile: string, ): PackageFileContent | null { - let fvmConfig: FvmConfig; + let flutterVersion: string | undefined; try { - fvmConfig = JSON.parse(content); + const config = Json.pipe(FvmConfig).parse(content); + flutterVersion = config.flutter ?? config.flutterSdkVersion; + + if (!flutterVersion) { + logger.debug( + { contents: config }, + 'FVM config does not have a flutter version specified', + ); + return null; + } } catch (err) { logger.debug({ packageFile, err }, 'Invalid FVM config'); return null; } - if (!fvmConfig.flutterSdkVersion) { - logger.debug( - { contents: fvmConfig }, - 'FVM config does not have flutterSdkVersion specified', - ); - return null; - } else if (!is.string(fvmConfig.flutterSdkVersion)) { - logger.debug({ contents: fvmConfig }, 'flutterSdkVersion must be a string'); - return null; - } - const dep: PackageDependency = { depName: 'flutter', - currentValue: fvmConfig.flutterSdkVersion, + currentValue: flutterVersion, datasource: FlutterVersionDatasource.id, packageName: 'flutter/flutter', }; diff --git a/lib/modules/manager/fvm/index.ts b/lib/modules/manager/fvm/index.ts index ebb67637e31e32..ae3f9cf8fee364 100644 --- a/lib/modules/manager/fvm/index.ts +++ b/lib/modules/manager/fvm/index.ts @@ -6,6 +6,6 @@ export { extractPackageFile } from './extract'; export const supportedDatasources = [FlutterVersionDatasource.id]; export const defaultConfig = { - fileMatch: ['(^|/)\\.fvm/fvm_config\\.json$'], + fileMatch: ['(^|/)\\.fvm/fvm_config\\.json$', '(^|/)\\.fvmrc$'], versioning: semverVersioning.id, }; diff --git a/lib/modules/manager/fvm/readme.md b/lib/modules/manager/fvm/readme.md index 5d4f3ab310c1a3..94011f171cd725 100644 --- a/lib/modules/manager/fvm/readme.md +++ b/lib/modules/manager/fvm/readme.md @@ -1 +1 @@ -Keeps the `.fvm/fvm_config.json` file updated. +Keeps the `.fvmrc` file or older `.fvm/fvm_config.json` file updated. diff --git a/lib/modules/manager/fvm/schema.ts b/lib/modules/manager/fvm/schema.ts new file mode 100644 index 00000000000000..67cb16bd6363ee --- /dev/null +++ b/lib/modules/manager/fvm/schema.ts @@ -0,0 +1,7 @@ +import { z } from 'zod'; + +export const FvmConfig = z.object({ + flutterSdkVersion: z.string().optional(), + flutter: z.string().optional(), +}); +export type FvmConfig = z.infer; diff --git a/lib/modules/manager/github-actions/__fixtures__/workflow_4.yml b/lib/modules/manager/github-actions/__fixtures__/workflow_4.yml index ae34907103bdcf..809518dcf51030 100644 --- a/lib/modules/manager/github-actions/__fixtures__/workflow_4.yml +++ b/lib/modules/manager/github-actions/__fixtures__/workflow_4.yml @@ -17,3 +17,6 @@ jobs: - uses: actions/checkout@01aecc#v2.1.0 - uses: actions/checkout@689fcce700ae7ffc576f2b029b51b2ffb66d3abd # comment containing 2.1.0 - uses: actions/checkout@689fcce700ae7ffc576f2b029b51b2ffb66d3abd # v2.1.0 additional comment + - uses: actions/checkout@689fcce700ae7ffc576f2b029b51b2ffb66d3abd # ratchet:actions/checkout@v2.1.0 + - uses: actions/checkout@689fcce700ae7ffc576f2b029b51b2ffb66d3abd # ratchet:exclude + - uses: actions-runner-controller/execute-assert-arc-e2e@f1d7c52253b89f0beae60141f8465d9495cdc2cf # actions-runner-controller-0.23.5 diff --git a/lib/modules/manager/github-actions/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/github-actions/__snapshots__/extract.spec.ts.snap index c10544c99c6d79..713985a87545cf 100644 --- a/lib/modules/manager/github-actions/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/github-actions/__snapshots__/extract.spec.ts.snap @@ -10,7 +10,6 @@ exports[`modules/manager/github-actions/extract extractPackageFile() extracts mu "depName": "actions/bin", "depType": "action", "replaceString": "actions/bin/shellcheck@master", - "skipReason": "invalid-version", "versioning": "docker", }, { @@ -127,7 +126,6 @@ exports[`modules/manager/github-actions/extract extractPackageFile() extracts mu "depName": "actions/bin", "depType": "action", "replaceString": "actions/bin/shellcheck@master", - "skipReason": "invalid-version", "versioning": "docker", }, { @@ -147,7 +145,6 @@ exports[`modules/manager/github-actions/extract extractPackageFile() extracts mu "depName": "actions/docker", "depType": "action", "replaceString": "actions/docker/cli@master", - "skipReason": "invalid-version", "versioning": "docker", }, { diff --git a/lib/modules/manager/github-actions/extract.spec.ts b/lib/modules/manager/github-actions/extract.spec.ts index 6cb38d7ac02909..b0b796bb30009d 100644 --- a/lib/modules/manager/github-actions/extract.spec.ts +++ b/lib/modules/manager/github-actions/extract.spec.ts @@ -371,7 +371,6 @@ describe('modules/manager/github-actions/extract', () => { { currentValue: '01aecc#v2.1.0', replaceString: 'actions/checkout@01aecc#v2.1.0', - skipReason: 'invalid-version', }, { currentDigest: '689fcce700ae7ffc576f2b029b51b2ffb66d3abd', @@ -385,7 +384,27 @@ describe('modules/manager/github-actions/extract', () => { replaceString: 'actions/checkout@689fcce700ae7ffc576f2b029b51b2ffb66d3abd # v2.1.0', }, + { + currentDigest: '689fcce700ae7ffc576f2b029b51b2ffb66d3abd', + currentValue: 'v2.1.0', + replaceString: + 'actions/checkout@689fcce700ae7ffc576f2b029b51b2ffb66d3abd # ratchet:actions/checkout@v2.1.0', + }, + { + currentDigest: '689fcce700ae7ffc576f2b029b51b2ffb66d3abd', + currentValue: undefined, + replaceString: + 'actions/checkout@689fcce700ae7ffc576f2b029b51b2ffb66d3abd # ratchet:exclude', + }, + { + currentDigest: 'f1d7c52253b89f0beae60141f8465d9495cdc2cf', + currentValue: 'actions-runner-controller-0.23.5', + replaceString: + 'actions-runner-controller/execute-assert-arc-e2e@f1d7c52253b89f0beae60141f8465d9495cdc2cf # actions-runner-controller-0.23.5', + }, ]); + + expect(res!.deps[14]).not.toHaveProperty('skipReason'); }); it('extracts actions with fqdn', () => { diff --git a/lib/modules/manager/github-actions/extract.ts b/lib/modules/manager/github-actions/extract.ts index 14ba5c4b99b5e1..c147df401c9180 100644 --- a/lib/modules/manager/github-actions/extract.ts +++ b/lib/modules/manager/github-actions/extract.ts @@ -15,7 +15,7 @@ import type { Workflow } from './types'; const dockerActionRe = regEx(/^\s+uses\s*: ['"]?docker:\/\/([^'"]+)\s*$/); const actionRe = regEx( - /^\s+-?\s+?uses\s*: (?['"]?(?https:\/\/[.\w-]+\/)?(?[\w-]+\/[.\w-]+)(?\/.*)?@(?[^\s'"]+)['"]?(?:\s+#\s*(?:renovate\s*:\s*)?(?:pin\s+|tag\s*=\s*)?@?(?v?\d+(?:\.\d+(?:\.\d+)?)?))?)/, + /^\s+-?\s+?uses\s*: (?['"]?(?https:\/\/[.\w-]+\/)?(?[\w-]+\/[.\w-]+)(?\/.*)?@(?[^\s'"]+)['"]?(?:\s+#\s*(((?:renovate\s*:\s*)?(?:pin\s+|tag\s*=\s*)?|(?:ratchet:[\w-]+\/[.\w-]+)?)@?(?([\w-]*-)?v?\d+(?:\.\d+(?:\.\d+)?)?)|(?:ratchet:exclude)))?)/, ); // SHA1 or SHA256, see https://github.blog/2020-10-19-git-2-29-released/ @@ -100,9 +100,6 @@ function extractWithRegex(content: string): PackageDependency[] { dep.currentDigestShort = currentValue; } else { dep.currentValue = currentValue; - if (!dockerVersioning.api.isValid(currentValue)) { - dep.skipReason = 'invalid-version'; - } } deps.push(dep); } diff --git a/lib/modules/manager/github-actions/readme.md b/lib/modules/manager/github-actions/readme.md index be99cb2597fb22..67857a0ab4fb74 100644 --- a/lib/modules/manager/github-actions/readme.md +++ b/lib/modules/manager/github-actions/readme.md @@ -1,7 +1,7 @@ The `github-actions` manager extracts dependencies from GitHub Actions workflow and workflow template files. It can also be used for Gitea and Forgejo Actions workflows as such are compatible with GitHub Actions workflows. -If you like to use digest pinning but want to follow the action version tag, you can use the following sample: +If you like to use digest pinning but want to follow the action version tag, you can use the sample below: ```yaml name: build @@ -15,8 +15,14 @@ jobs: - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 ``` -Renovate will update the commit SHA but follow the GitHub tag you specified. +Renovate will update the commit SHA according to the GitHub tag you specified. Renovate can update digests that use SHA1 and SHA256 algorithms. +The GitHub tag is in the format of `(prefix-)(v)1.0.0`, where `prefix` and `v` are optional and `1.0.0` is the version number. +Here are the examples of valid GitHub tags: +`1.0.1`, `1.0`, `1`, +`v1.0.1`, `v1.0`, `v1`, +`prefix-1.0.1`, `prefix-1.0`, `prefix-1`, +`prefix-v1.0.1`, `prefix-v1.0`, `prefix-v1`. If you want to automatically pin action digests add the `helpers:pinGitHubActionDigests` preset to the `extends` array: @@ -34,9 +40,16 @@ name: build on: [push] env: - RUNNER: ubuntu-20.04 + RUNNER: ubuntu-22.04 jobs: build: runs-on: ${{ env.RUNNER }} ``` + +The `github-action` manager understands `ratchet` comments, like `# ratchet:actions/checkout@v2.1.0`. +This means that Renovate will: + +- update the version of a _pinned_ Ratchet version if needed +- not delete Ratchet comments after parsing them +- keep `# ratchet:exclude` comments diff --git a/lib/modules/manager/gitlabci/__fixtures__/gitlab-ci.multi-doc.yaml b/lib/modules/manager/gitlabci/__fixtures__/gitlab-ci.multi-doc.yaml new file mode 100644 index 00000000000000..c57ce8e5e77045 --- /dev/null +++ b/lib/modules/manager/gitlabci/__fixtures__/gitlab-ci.multi-doc.yaml @@ -0,0 +1,32 @@ +image: + # comment + name: renovate/renovate:19.70.8-slim + +services: + # comment + - mariadb:10.4.11 + # another comment + - other/image:1.0.0 + +include: + - local: 'lib/modules/manager/gitlabci/__fixtures__/include.yml' + - local: 'lib/modules/manager/gitlabci/__fixtures__/include.yml' # Loop detection + - local: 'lib/modules/manager/gitlabci/__fixtures__/include.1.yml' + - local: 'lib/modules/manager/gitlabci/__fixtures__/*/.gitlab-ci.yml' + - project: 'my-group/my-project' + ref: master + file: '/templates/.gitlab-ci-template.yml' + +script: + - !reference [.setup, script] +--- +image: + # comment + name: renovate/renovate:19.70.8-slim + +services: + # comment + - mariadb:10.4.11 + # another comment + - name: other/image:1.0.0 + alias: imagealias diff --git a/lib/modules/manager/gitlabci/extract.spec.ts b/lib/modules/manager/gitlabci/extract.spec.ts index 820bef53ca564b..84ab81e28c5b9f 100644 --- a/lib/modules/manager/gitlabci/extract.spec.ts +++ b/lib/modules/manager/gitlabci/extract.spec.ts @@ -38,6 +38,16 @@ describe('modules/manager/gitlabci/extract', () => { ).toBeNull(); }); + it('extracts from multidoc yaml', async () => { + const res = await extractAllPackageFiles(config, [ + 'lib/modules/manager/gitlabci/__fixtures__/gitlab-ci.multi-doc.yaml', + ]); + expect(res).toHaveLength(3); + + const deps = res?.map((entry) => entry.deps).flat(); + expect(deps).toHaveLength(8); + }); + it('extracts multiple included image lines', async () => { const res = await extractAllPackageFiles(config, [ 'lib/modules/manager/gitlabci/__fixtures__/gitlab-ci.3.yaml', @@ -45,12 +55,7 @@ describe('modules/manager/gitlabci/extract', () => { expect(res).toMatchSnapshot(); expect(res).toHaveLength(3); - const deps: PackageDependency[] = []; - res?.forEach((e) => { - e.deps.forEach((d) => { - deps.push(d); - }); - }); + const deps = res?.map((entry) => entry.deps).flat(); expect(deps).toHaveLength(5); }); @@ -349,6 +354,69 @@ describe('modules/manager/gitlabci/extract', () => { expect(extractFromJob({ image: 'image:test' })).toEqual(expectedRes); }); + it('extracts component references via registry aliases', () => { + const registryAliases = { + $CI_SERVER_HOST: 'gitlab.example.com', + }; + const content = codeBlock` + include: + - component: $CI_SERVER_HOST/an-org/a-project/a-component@1.0 + inputs: + stage: build + - component: $CI_SERVER_HOST/an-org/a-subgroup/a-project/a-component@e3262fdd0914fa823210cdb79a8c421e2cef79d8 + - component: $CI_SERVER_HOST/an-org/a-subgroup/another-project/a-component@main + - component: $CI_SERVER_HOST/another-org/a-project/a-component@~latest + inputs: + stage: test + - component: $CI_SERVER_HOST/malformed-component-reference + - component: + malformed: true + - component: $CI_SERVER_HOST/an-org/a-component@1.0 + - component: other-gitlab.example.com/an-org/a-project/a-component@1.0 + `; + const res = extractPackageFile(content, '', { + registryAliases, + }); + expect(res?.deps).toMatchObject([ + { + currentValue: '1.0', + datasource: 'gitlab-tags', + depName: 'an-org/a-project', + depType: 'repository', + registryUrls: ['https://gitlab.example.com'], + }, + { + currentValue: 'e3262fdd0914fa823210cdb79a8c421e2cef79d8', + datasource: 'gitlab-tags', + depName: 'an-org/a-subgroup/a-project', + depType: 'repository', + registryUrls: ['https://gitlab.example.com'], + }, + { + currentValue: 'main', + datasource: 'gitlab-tags', + depName: 'an-org/a-subgroup/another-project', + depType: 'repository', + registryUrls: ['https://gitlab.example.com'], + }, + { + currentValue: '~latest', + datasource: 'gitlab-tags', + depName: 'another-org/a-project', + depType: 'repository', + registryUrls: ['https://gitlab.example.com'], + skipReason: 'unsupported-version', + }, + { + currentValue: '1.0', + datasource: 'gitlab-tags', + depName: 'an-org/a-project', + depType: 'repository', + registryUrls: ['https://other-gitlab.example.com'], + }, + ]); + }); + it('extracts component references', () => { const content = codeBlock` include: diff --git a/lib/modules/manager/gitlabci/extract.ts b/lib/modules/manager/gitlabci/extract.ts index 7df1894c615d29..4a0e1872d76be2 100644 --- a/lib/modules/manager/gitlabci/extract.ts +++ b/lib/modules/manager/gitlabci/extract.ts @@ -3,7 +3,7 @@ import { logger } from '../../../logger'; import { readLocalFile } from '../../../util/fs'; import { regEx } from '../../../util/regex'; import { trimLeadingSlash } from '../../../util/url'; -import { parseSingleYaml } from '../../../util/yaml'; +import { parseYaml } from '../../../util/yaml'; import { GitlabTagsDatasource } from '../../datasource/gitlab-tags'; import type { ExtractConfig, @@ -120,6 +120,7 @@ function getAllIncludeComponents( function extractDepFromIncludeComponent( includeComponent: GitlabIncludeComponent, + registryAliases?: Record, ): PackageDependency | null { const componentReference = componentReferenceRegex.exec( includeComponent.component, @@ -139,6 +140,10 @@ function extractDepFromIncludeComponent( ); return null; } + const aliasValue = registryAliases?.[componentReference.fqdn]; + if (aliasValue) { + componentReference.fqdn = aliasValue; + } const dep: PackageDependency = { datasource: GitlabTagsDatasource.id, @@ -165,43 +170,57 @@ export function extractPackageFile( let deps: PackageDependency[] = []; try { // TODO: use schema (#9610) - const doc = parseSingleYaml(replaceReferenceTags(content), { - json: true, - }); - if (is.object(doc)) { - for (const [property, value] of Object.entries(doc)) { - switch (property) { - case 'image': - { - const dep = extractFromImage( - value as Image, - config.registryAliases, - ); - if (dep) { - deps.push(dep); + const docs = parseYaml( + replaceReferenceTags(content), + null, + { + json: true, + }, + ); + for (const doc of docs) { + if (is.object(doc)) { + for (const [property, value] of Object.entries(doc)) { + switch (property) { + case 'image': + { + const dep = extractFromImage( + value as Image, + config.registryAliases, + ); + if (dep) { + deps.push(dep); + } } - } - break; + break; - case 'services': - deps.push( - ...extractFromServices(value as Services, config.registryAliases), - ); - break; + case 'services': + deps.push( + ...extractFromServices( + value as Services, + config.registryAliases, + ), + ); + break; - default: - deps.push(...extractFromJob(value as Job, config.registryAliases)); - break; + default: + deps.push( + ...extractFromJob(value as Job, config.registryAliases), + ); + break; + } } + deps = deps.filter(is.truthy); } - deps = deps.filter(is.truthy); - } - const includedComponents = getAllIncludeComponents(doc); - for (const includedComponent of includedComponents) { - const dep = extractDepFromIncludeComponent(includedComponent); - if (dep) { - deps.push(dep); + const includedComponents = getAllIncludeComponents(doc); + for (const includedComponent of includedComponents) { + const dep = extractDepFromIncludeComponent( + includedComponent, + config.registryAliases, + ); + if (dep) { + deps.push(dep); + } } } } catch (err) /* istanbul ignore next */ { @@ -241,10 +260,10 @@ export async function extractAllPackageFiles( ); continue; } - let doc: GitlabPipeline; + let docs: GitlabPipeline[]; try { // TODO: use schema (#9610) - doc = parseSingleYaml(replaceReferenceTags(content), { + docs = parseYaml(replaceReferenceTags(content), null, { json: true, }); } catch (err) { @@ -255,20 +274,22 @@ export async function extractAllPackageFiles( continue; } - if (is.array(doc?.include)) { - for (const includeObj of doc.include.filter(isGitlabIncludeLocal)) { - const fileObj = trimLeadingSlash(includeObj.local); + for (const doc of docs) { + if (is.array(doc?.include)) { + for (const includeObj of doc.include.filter(isGitlabIncludeLocal)) { + const fileObj = trimLeadingSlash(includeObj.local); + if (!seen.has(fileObj)) { + seen.add(fileObj); + filesToExamine.push(fileObj); + } + } + } else if (is.string(doc?.include)) { + const fileObj = trimLeadingSlash(doc.include); if (!seen.has(fileObj)) { seen.add(fileObj); filesToExamine.push(fileObj); } } - } else if (is.string(doc?.include)) { - const fileObj = trimLeadingSlash(doc.include); - if (!seen.has(fileObj)) { - seen.add(fileObj); - filesToExamine.push(fileObj); - } } const result = extractPackageFile(content, file, config); diff --git a/lib/modules/manager/gitlabci/readme.md b/lib/modules/manager/gitlabci/readme.md index 065cdf93e5a4a6..121cecf3e14cab 100644 --- a/lib/modules/manager/gitlabci/readme.md +++ b/lib/modules/manager/gitlabci/readme.md @@ -1,6 +1,6 @@ Extracts Docker dependencies from `gitlab-ci.yml` files. -If you need to change the versioning format, read the [versioning](../../versioning.md) documentation to learn more. +If you need to change the versioning format, read the [versioning](../../versioning/index.md) documentation to learn more. If you use Gitlab Dependency Proxy then you can use these predefined variables as prefixes for your image: diff --git a/lib/modules/manager/gomod/__fixtures__/3/go-mod b/lib/modules/manager/gomod/__fixtures__/3/go-mod index e8000701b03fcb..973a7c3dcee593 100644 --- a/lib/modules/manager/gomod/__fixtures__/3/go-mod +++ b/lib/modules/manager/gomod/__fixtures__/3/go-mod @@ -131,3 +131,6 @@ replace ( k8s.io/sample-cli-plugin => k8s.io/sample-cli-plugin v0.17.3 k8s.io/sample-controller => k8s.io/sample-controller v0.17.3 ) + +toolchain go1.22.1 + diff --git a/lib/modules/manager/gomod/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/gomod/__snapshots__/extract.spec.ts.snap index f089350ae9da1d..93e19c1eafdfd8 100644 --- a/lib/modules/manager/gomod/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/gomod/__snapshots__/extract.spec.ts.snap @@ -721,12 +721,13 @@ exports[`modules/manager/gomod/extract extractPackageFile() extracts single-line }, { "currentValue": "abcdef1", + "datasource": "go", "depName": "github.com/rarkins/foo", "depType": "require", "managerData": { "lineNumber": 6, }, - "skipReason": "unsupported-version", + "skipReason": "invalid-version", }, { "currentValue": "v1.0.0", @@ -746,6 +747,15 @@ exports[`modules/manager/gomod/extract extractPackageFile() extracts single-line "lineNumber": 8, }, }, + { + "datasource": "go", + "depName": "../errors", + "depType": "replace", + "managerData": { + "lineNumber": 10, + }, + "skipReason": "local-dependency", + }, { "currentValue": "v0.0.0", "datasource": "go", diff --git a/lib/modules/manager/gomod/extract.spec.ts b/lib/modules/manager/gomod/extract.spec.ts index de4f03a2235b92..97d093d62b4ac6 100644 --- a/lib/modules/manager/gomod/extract.spec.ts +++ b/lib/modules/manager/gomod/extract.spec.ts @@ -1,3 +1,4 @@ +import { codeBlock } from 'common-tags'; import { Fixtures } from '../../../../test/fixtures'; import { extractPackageFile } from '.'; @@ -13,11 +14,11 @@ describe('modules/manager/gomod/extract', () => { it('extracts single-line requires', () => { const res = extractPackageFile(gomod1)?.deps; expect(res).toMatchSnapshot(); - expect(res).toHaveLength(9); + expect(res).toHaveLength(10); expect(res?.filter((e) => e.depType === 'require')).toHaveLength(7); expect(res?.filter((e) => e.depType === 'indirect')).toHaveLength(1); - expect(res?.filter((e) => e.skipReason)).toHaveLength(1); - expect(res?.filter((e) => e.depType === 'replace')).toHaveLength(1); + expect(res?.filter((e) => e.skipReason)).toHaveLength(2); + expect(res?.filter((e) => e.depType === 'replace')).toHaveLength(2); }); it('extracts multi-line requires', () => { @@ -29,38 +30,39 @@ describe('modules/manager/gomod/extract', () => { }); it('ignores empty spaces in multi-line requires', () => { - const goMod = ` -module github.com/renovate-tests/gomod -go 1.19 -require ( - cloud.google.com/go v0.45.1 + const goMod = codeBlock` + module github.com/renovate-tests/gomod + go 1.19 + require ( + cloud.google.com/go v0.45.1 - github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 // indirect -) -`; + github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 // indirect + ) + `; const res = extractPackageFile(goMod)?.deps; expect(res).toHaveLength(3); }); it('extracts replace directives from multi-line and single line', () => { - const goMod = ` -module github.com/renovate-tests/gomod -go 1.18 -replace golang.org/x/foo => github.com/pravesht/gocql v0.0.0 -replace ( - k8s.io/client-go => k8s.io/client-go v0.21.9 - ) -replace ( - k8s.io/cloud-provider => k8s.io/cloud-provider v0.17.3 - k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.17.3 // indirect - k8s.io/code-generator => k8s.io/code-generator v0.17.3 -)`; + const goMod = codeBlock` + module github.com/renovate-tests/gomod + go 1.18 + replace golang.org/x/foo => github.com/pravesht/gocql v0.0.0 + replace ( + k8s.io/client-go => k8s.io/client-go v0.21.9 + ) + replace ( + k8s.io/cloud-provider => k8s.io/cloud-provider v0.17.3 + k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.17.3 // indirect + k8s.io/code-generator => k8s.io/code-generator v0.17.3 + ) + `; const res = extractPackageFile(goMod); expect(res).toEqual({ deps: [ { managerData: { - lineNumber: 2, + lineNumber: 1, }, depName: 'go', depType: 'golang', @@ -70,7 +72,7 @@ replace ( }, { managerData: { - lineNumber: 3, + lineNumber: 2, }, depName: 'github.com/pravesht/gocql', depType: 'replace', @@ -79,7 +81,7 @@ replace ( }, { managerData: { - lineNumber: 5, + lineNumber: 4, multiLine: true, }, depName: 'k8s.io/client-go', @@ -89,7 +91,7 @@ replace ( }, { managerData: { - lineNumber: 8, + lineNumber: 7, multiLine: true, }, depName: 'k8s.io/cloud-provider', @@ -99,7 +101,7 @@ replace ( }, { managerData: { - lineNumber: 9, + lineNumber: 8, multiLine: true, }, depName: 'k8s.io/cluster-bootstrap', @@ -110,7 +112,7 @@ replace ( }, { managerData: { - lineNumber: 10, + lineNumber: 9, multiLine: true, }, depName: 'k8s.io/code-generator', @@ -121,5 +123,47 @@ replace ( ], }); }); + + it('extracts the toolchain directive', () => { + const goMod = codeBlock` + module github.com/renovate-tests/gomod + go 1.21 + toolchain go1.21.7 + replace golang.org/x/foo => github.com/pravesht/gocql v0.0.0 + `; + const res = extractPackageFile(goMod); + expect(res).toEqual({ + deps: [ + { + managerData: { + lineNumber: 1, + }, + depName: 'go', + depType: 'golang', + currentValue: '1.21', + datasource: 'golang-version', + versioning: 'go-mod-directive', + }, + { + managerData: { + lineNumber: 2, + }, + depName: 'go', + depType: 'toolchain', + currentValue: '1.21.7', + datasource: 'golang-version', + }, + { + managerData: { + lineNumber: 3, + }, + depName: 'github.com/pravesht/gocql', + depType: 'replace', + currentValue: 'v0.0.0', + datasource: 'go', + }, + ], + }); + }); }); }); diff --git a/lib/modules/manager/gomod/extract.ts b/lib/modules/manager/gomod/extract.ts index 5f09a2bf5f40db..731bd2b7c3687f 100644 --- a/lib/modules/manager/gomod/extract.ts +++ b/lib/modules/manager/gomod/extract.ts @@ -1,151 +1,27 @@ -import semver from 'semver'; -import { logger } from '../../../logger'; -import { newlineRegex, regEx } from '../../../util/regex'; -import { GoDatasource } from '../../datasource/go'; -import { GolangVersionDatasource } from '../../datasource/golang-version'; -import { isVersion } from '../../versioning/semver'; +import { newlineRegex } from '../../../util/regex'; import type { PackageDependency, PackageFileContent } from '../types'; -import type { MultiLineParseResult } from './types'; +import { parseLine } from './line-parser'; -function getDep( - lineNumber: number, - match: RegExpMatchArray, - type: string, -): PackageDependency { - const [, , currentValue] = match; - let [, depName] = match; - depName = depName.replace(regEx(/"/g), ''); - const dep: PackageDependency = { - managerData: { - lineNumber, - }, - depName, - depType: type, - currentValue, - }; - if (isVersion(currentValue)) { - dep.datasource = GoDatasource.id; - } else { - dep.skipReason = 'unsupported-version'; - } - const digestMatch = regEx(GoDatasource.pversionRegexp).exec(currentValue); - if (digestMatch?.groups?.digest) { - dep.currentDigest = digestMatch.groups.digest; - dep.digestOneAndOnly = true; - dep.versioning = 'loose'; - } - return dep; -} - -function getGoDep(lineNumber: number, goVer: string): PackageDependency { - return { - managerData: { - lineNumber, - }, - depName: 'go', - depType: 'golang', - currentValue: goVer, - datasource: GolangVersionDatasource.id, - versioning: 'go-mod-directive', - }; -} - -export function extractPackageFile( - content: string, - packageFile?: string, -): PackageFileContent | null { - logger.trace({ content }, 'gomod.extractPackageFile()'); +export function extractPackageFile(content: string): PackageFileContent | null { const deps: PackageDependency[] = []; - try { - const lines = content.split(newlineRegex); - for (let lineNumber = 0; lineNumber < lines.length; lineNumber += 1) { - const line = lines[lineNumber]; - const goVer = line.startsWith('go ') ? line.replace('go ', '') : null; - if (goVer && semver.validRange(goVer)) { - const dep = getGoDep(lineNumber, goVer); - deps.push(dep); - } - const replaceMatch = regEx( - /^replace\s+[^\s]+[\s]+[=][>]\s+([^\s]+)\s+([^\s]+)/, - ).exec(line); - if (replaceMatch) { - const dep = getDep(lineNumber, replaceMatch, 'replace'); - deps.push(dep); - } - const requireMatch = regEx(/^require\s+([^\s]+)\s+([^\s]+)/).exec(line); - if (requireMatch) { - if (line.endsWith('// indirect')) { - logger.trace({ lineNumber }, `indirect line: "${line}"`); - const dep = getDep(lineNumber, requireMatch, 'indirect'); - dep.enabled = false; - deps.push(dep); - } else { - logger.trace({ lineNumber }, `require line: "${line}"`); - const dep = getDep(lineNumber, requireMatch, 'require'); - deps.push(dep); - } - } - if (line.trim() === 'require (') { - logger.trace(`Matched multi-line require on line ${lineNumber}`); - const matcher = regEx(/^\s+([^\s]+)\s+([^\s]+)/); - const { reachedLine, detectedDeps } = parseMultiLine( - lineNumber, - lines, - matcher, - 'require', - ); - lineNumber = reachedLine; - deps.push(...detectedDeps); - } else if (line.trim() === 'replace (') { - logger.trace(`Matched multi-line replace on line ${lineNumber}`); - const matcher = regEx(/^\s+[^\s]+[\s]+[=][>]\s+([^\s]+)\s+([^\s]+)/); - const { reachedLine, detectedDeps } = parseMultiLine( - lineNumber, - lines, - matcher, - 'replace', - ); - lineNumber = reachedLine; - deps.push(...detectedDeps); - } + + const lines = content.split(newlineRegex); + for (let lineNumber = 0; lineNumber < lines.length; lineNumber += 1) { + const line = lines[lineNumber]; + const dep = parseLine(line); + if (!dep) { + continue; } - } catch (err) /* istanbul ignore next */ { - logger.warn({ err, packageFile }, 'Error extracting go modules'); + + dep.managerData ??= {}; + dep.managerData.lineNumber = lineNumber; + + deps.push(dep); } + if (!deps.length) { return null; } - return { deps }; -} -function parseMultiLine( - startingLine: number, - lines: string[], - matchRegex: RegExp, - blockType: 'require' | 'replace', -): MultiLineParseResult { - const deps: PackageDependency[] = []; - let lineNumber = startingLine; - let line = ''; - do { - lineNumber += 1; - line = lines[lineNumber]; - const multiMatch = matchRegex.exec(line); - logger.trace(`${blockType}: "${line}"`); - if (multiMatch && !line.endsWith('// indirect')) { - logger.trace({ lineNumber }, `${blockType} line: "${line}"`); - const dep = getDep(lineNumber, multiMatch, blockType); - dep.managerData!.multiLine = true; - deps.push(dep); - } else if (multiMatch && line.endsWith('// indirect')) { - logger.trace({ lineNumber }, `${blockType} indirect line: "${line}"`); - const dep = getDep(lineNumber, multiMatch, 'indirect'); - dep.managerData!.multiLine = true; - dep.enabled = false; - deps.push(dep); - } else if (line.trim() !== ')') { - logger.trace(`No multi-line match: ${line}`); - } - } while (line.trim() !== ')'); - return { reachedLine: lineNumber, detectedDeps: deps }; + return { deps }; } diff --git a/lib/modules/manager/gomod/line-parser.spec.ts b/lib/modules/manager/gomod/line-parser.spec.ts new file mode 100644 index 00000000000000..deef02e43f98bf --- /dev/null +++ b/lib/modules/manager/gomod/line-parser.spec.ts @@ -0,0 +1,280 @@ +import { parseLine } from './line-parser'; + +describe('modules/manager/gomod/line-parser', () => { + it('should return null for invalid input', () => { + expect(parseLine('invalid')).toBeNull(); + }); + + it('should parse go version', () => { + const line = 'go 1.16'; + const res = parseLine(line); + expect(res).toStrictEqual({ + currentValue: '1.16', + datasource: 'golang-version', + depName: 'go', + depType: 'golang', + versioning: 'go-mod-directive', + }); + }); + + it('should skip invalid go version', () => { + const line = 'go invalid'; + const res = parseLine(line); + expect(res).toStrictEqual({ + currentValue: 'invalid', + datasource: 'golang-version', + depName: 'go', + depType: 'golang', + skipReason: 'invalid-version', + versioning: 'go-mod-directive', + }); + }); + + it('should parse toolchain version', () => { + const line = 'toolchain go1.16'; + const res = parseLine(line); + expect(res).toStrictEqual({ + currentValue: '1.16', + datasource: 'golang-version', + depName: 'go', + depType: 'toolchain', + skipReason: 'invalid-version', + }); + }); + + it('should skip invalid toolchain version', () => { + const line = 'toolchain go-invalid'; + const res = parseLine(line); + expect(res).toStrictEqual({ + currentValue: '-invalid', + datasource: 'golang-version', + depName: 'go', + depType: 'toolchain', + skipReason: 'invalid-version', + }); + }); + + it('should parse require definition', () => { + const line = 'require foo/foo v1.2'; + const res = parseLine(line); + expect(res).toStrictEqual({ + currentValue: 'v1.2', + datasource: 'go', + depName: 'foo/foo', + depType: 'require', + skipReason: 'invalid-version', + }); + }); + + it('should parse require definition with pseudo-version', () => { + const line = 'require foo/foo v0.0.0-20210101000000-000000000000'; + const res = parseLine(line); + expect(res).toStrictEqual({ + currentDigest: '000000000000', + currentValue: 'v0.0.0-20210101000000-000000000000', + datasource: 'go', + depName: 'foo/foo', + depType: 'require', + digestOneAndOnly: true, + versioning: 'loose', + }); + }); + + it('should parse require multi-line', () => { + const line = ' foo/foo v1.2'; + const res = parseLine(line); + expect(res).toStrictEqual({ + currentValue: 'v1.2', + datasource: 'go', + depName: 'foo/foo', + depType: 'require', + managerData: { + multiLine: true, + }, + skipReason: 'invalid-version', + }); + }); + + it('should parse require definition with quotes', () => { + const line = 'require "foo/foo" v1.2'; + const res = parseLine(line); + expect(res).toStrictEqual({ + currentValue: 'v1.2', + datasource: 'go', + depName: 'foo/foo', + depType: 'require', + skipReason: 'invalid-version', + }); + }); + + it('should parse require multi-line definition with quotes', () => { + const line = ' "foo/foo" v1.2'; + const res = parseLine(line); + expect(res).toStrictEqual({ + currentValue: 'v1.2', + datasource: 'go', + depName: 'foo/foo', + depType: 'require', + managerData: { + multiLine: true, + }, + skipReason: 'invalid-version', + }); + }); + + it('should parse require definition with indirect dependency', () => { + const line = 'require foo/foo v1.2 // indirect'; + const res = parseLine(line); + expect(res).toStrictEqual({ + currentValue: 'v1.2', + datasource: 'go', + depName: 'foo/foo', + depType: 'indirect', + enabled: false, + skipReason: 'invalid-version', + }); + }); + + it('should parse require multi-line definition with indirect dependency', () => { + const line = ' foo/foo v1.2 // indirect'; + const res = parseLine(line); + expect(res).toStrictEqual({ + currentValue: 'v1.2', + datasource: 'go', + depName: 'foo/foo', + depType: 'indirect', + enabled: false, + managerData: { + multiLine: true, + }, + skipReason: 'invalid-version', + }); + }); + + it('should parse replace definition', () => { + const line = 'replace foo/foo => bar/bar'; + const res = parseLine(line); + expect(res).toStrictEqual({ + datasource: 'go', + depName: 'bar/bar', + depType: 'replace', + skipReason: 'unspecified-version', + }); + }); + + it('should parse replace multi-line definition', () => { + const line = ' foo/foo => bar/bar'; + const res = parseLine(line); + expect(res).toStrictEqual({ + datasource: 'go', + depName: 'bar/bar', + depType: 'replace', + managerData: { + multiLine: true, + }, + skipReason: 'unspecified-version', + }); + }); + + it('should parse replace definition with quotes', () => { + const line = 'replace "foo/foo" => "bar/bar"'; + const res = parseLine(line); + expect(res).toStrictEqual({ + datasource: 'go', + depName: 'bar/bar', + depType: 'replace', + skipReason: 'unspecified-version', + }); + }); + + it('should parse replace multi-line definition with quotes', () => { + const line = ' "foo/foo" => "bar/bar"'; + const res = parseLine(line); + expect(res).toStrictEqual({ + datasource: 'go', + depName: 'bar/bar', + depType: 'replace', + managerData: { + multiLine: true, + }, + skipReason: 'unspecified-version', + }); + }); + + it('should parse replace definition with version', () => { + const line = 'replace foo/foo => bar/bar v1.2'; + const res = parseLine(line); + expect(res).toStrictEqual({ + currentValue: 'v1.2', + datasource: 'go', + depName: 'bar/bar', + depType: 'replace', + skipReason: 'invalid-version', + }); + }); + + it('should parse replace definition with pseudo-version', () => { + const line = + 'replace foo/foo => bar/bar v0.0.0-20210101000000-000000000000'; + const res = parseLine(line); + expect(res).toStrictEqual({ + currentDigest: '000000000000', + currentValue: 'v0.0.0-20210101000000-000000000000', + datasource: 'go', + depName: 'bar/bar', + depType: 'replace', + digestOneAndOnly: true, + versioning: 'loose', + }); + }); + + it('should parse replace indirect definition', () => { + const line = 'replace foo/foo => bar/bar v1.2 // indirect'; + const res = parseLine(line); + expect(res).toStrictEqual({ + currentValue: 'v1.2', + datasource: 'go', + depName: 'bar/bar', + depType: 'indirect', + enabled: false, + skipReason: 'invalid-version', + }); + }); + + it('should parse replace multi-line definition with version', () => { + const line = ' foo/foo => bar/bar v1.2'; + const res = parseLine(line); + expect(res).toStrictEqual({ + currentValue: 'v1.2', + datasource: 'go', + depName: 'bar/bar', + depType: 'replace', + managerData: { + multiLine: true, + }, + skipReason: 'invalid-version', + }); + }); + + it('should parse replace definition pointing to relative local path', () => { + const line = 'replace foo/foo => ../bar'; + const res = parseLine(line); + expect(res).toStrictEqual({ + datasource: 'go', + depName: '../bar', + depType: 'replace', + skipReason: 'local-dependency', + }); + }); + + it('should parse replace definition pointing to absolute local path', () => { + const line = 'replace foo/foo => /bar'; + const res = parseLine(line); + expect(res).toStrictEqual({ + datasource: 'go', + depName: '/bar', + depType: 'replace', + skipReason: 'local-dependency', + }); + }); +}); diff --git a/lib/modules/manager/gomod/line-parser.ts b/lib/modules/manager/gomod/line-parser.ts new file mode 100644 index 00000000000000..58591c7c502cef --- /dev/null +++ b/lib/modules/manager/gomod/line-parser.ts @@ -0,0 +1,154 @@ +import semver from 'semver'; +import { regEx } from '../../../util/regex'; +import { GoDatasource } from '../../datasource/go'; +import { GolangVersionDatasource } from '../../datasource/golang-version'; +import { isVersion } from '../../versioning/semver'; +import type { PackageDependency } from '../types'; + +function trimQuotes(str: string): string { + return str.replace(regEx(/^"(.*)"$/), '$1'); +} + +const requireRegex = regEx( + /^(?require)?\s+(?[^\s]+\/[^\s]+)\s+(?[^\s]+)(?:\s*\/\/\s*(?[^\s]+)\s*)?$/, +); + +const replaceRegex = regEx( + /^(?replace)?\s+(?[^\s]+\/[^\s]+)\s*=>\s*(?[^\s]+)(?:\s+(?[^\s]+))?(?:\s*\/\/\s*(?[^\s]+)\s*)?$/, +); + +const goVersionRegex = regEx(/^\s*go\s+(?[^\s]+)\s*$/); + +const toolchainVersionRegex = regEx(/^\s*toolchain\s+go(?[^\s]+)\s*$/); + +const pseudoVersionRegex = regEx(GoDatasource.pversionRegexp); + +function extractDigest(input: string): string | undefined { + const match = pseudoVersionRegex.exec(input); + return match?.groups?.digest; +} + +export function parseLine(input: string): PackageDependency | null { + const goVersionMatches = goVersionRegex.exec(input)?.groups; + if (goVersionMatches) { + const { version: currentValue } = goVersionMatches; + + const dep: PackageDependency = { + datasource: GolangVersionDatasource.id, + versioning: 'go-mod-directive', + depType: 'golang', + depName: 'go', + currentValue, + }; + + if (!semver.validRange(currentValue)) { + dep.skipReason = 'invalid-version'; + } + + return dep; + } + + const toolchainMatches = toolchainVersionRegex.exec(input)?.groups; + if (toolchainMatches) { + const { version: currentValue } = toolchainMatches; + + const dep: PackageDependency = { + datasource: GolangVersionDatasource.id, + depType: 'toolchain', + depName: 'go', + currentValue, + }; + + if (!semver.valid(currentValue)) { + dep.skipReason = 'invalid-version'; + } + + return dep; + } + + const requireMatches = requireRegex.exec(input)?.groups; + if (requireMatches) { + const { keyword, module, version: currentValue, comment } = requireMatches; + + const depName = trimQuotes(module); + + const dep: PackageDependency = { + datasource: GoDatasource.id, + depType: 'require', + depName, + currentValue, + }; + + if (isVersion(currentValue)) { + const digest = extractDigest(currentValue); + if (digest) { + dep.currentDigest = digest; + dep.digestOneAndOnly = true; + dep.versioning = 'loose'; + } + } else { + dep.skipReason = 'invalid-version'; + } + + if (comment === 'indirect') { + dep.depType = 'indirect'; + dep.enabled = false; + } + + if (!keyword) { + dep.managerData = { multiLine: true }; + } + + return dep; + } + + const replaceMatches = replaceRegex.exec(input)?.groups; + if (replaceMatches) { + const { + keyword, + replacement, + version: currentValue, + comment, + } = replaceMatches; + + const depName = trimQuotes(replacement); + + const dep: PackageDependency = { + datasource: GoDatasource.id, + depType: 'replace', + depName, + currentValue, + }; + + if (isVersion(currentValue)) { + const digest = extractDigest(currentValue); + if (digest) { + dep.currentDigest = digest; + dep.digestOneAndOnly = true; + dep.versioning = 'loose'; + } + } else if (currentValue) { + dep.skipReason = 'invalid-version'; + } else { + dep.skipReason = 'unspecified-version'; + delete dep.currentValue; + } + + if (comment === 'indirect') { + dep.depType = 'indirect'; + dep.enabled = false; + } + + if (!keyword) { + dep.managerData = { multiLine: true }; + } + + if (depName.startsWith('/') || depName.startsWith('.')) { + dep.skipReason = 'local-dependency'; + } + + return dep; + } + + return null; +} diff --git a/lib/modules/manager/gomod/update.spec.ts b/lib/modules/manager/gomod/update.spec.ts index 06eff90aa8b80e..c6cce004636d5a 100644 --- a/lib/modules/manager/gomod/update.spec.ts +++ b/lib/modules/manager/gomod/update.spec.ts @@ -32,6 +32,18 @@ describe('modules/manager/gomod/update', () => { expect(res).toContain(upgrade.newValue); }); + it('replaces go toolchain', () => { + const upgrade = { + depName: 'go', + managerData: { lineNumber: 134 }, + newValue: '1.22.2', + depType: 'toolchain', + }; + const res = updateDependency({ fileContent: gomod3, upgrade }); + expect(res).not.toEqual(gomod3); + expect(res).toContain(upgrade.newValue); + }); + it('replaces two values in one file', () => { const upgrade1 = { depName: 'github.com/pkg/errors', diff --git a/lib/modules/manager/gomod/update.ts b/lib/modules/manager/gomod/update.ts index 2a446db9edd1a2..54a9e8c6e9c62f 100644 --- a/lib/modules/manager/gomod/update.ts +++ b/lib/modules/manager/gomod/update.ts @@ -34,6 +34,7 @@ export function updateDependency({ return null; } const lineToChange = lines[upgrade.managerData.lineNumber]; + logger.trace({ upgrade, lineToChange }, 'go.mod current line'); if ( !lineToChange.includes(depNameNoVersion) && !lineToChange.includes('rethinkdb/rethinkdb-go.v5') @@ -46,8 +47,10 @@ export function updateDependency({ } let updateLineExp: RegExp | undefined; - if (depType === 'golang') { - updateLineExp = regEx(/(?go)(?\s+)[^\s]+/); + if (depType === 'golang' || depType === 'toolchain') { + updateLineExp = regEx( + /(?(?:toolchain )?go)(?\s*)([^\s]+|[\w]+)/, + ); } if (depType === 'replace') { if (upgrade.managerData.multiLine) { @@ -69,7 +72,7 @@ export function updateDependency({ } } if (updateLineExp && !updateLineExp.test(lineToChange)) { - logger.debug('No image line found'); + logger.debug('No line found to update'); return null; } let newLine: string; diff --git a/lib/modules/manager/gradle-wrapper/utils.ts b/lib/modules/manager/gradle-wrapper/utils.ts index 5e75e3c2e14b77..713a6f8b265e3f 100644 --- a/lib/modules/manager/gradle-wrapper/utils.ts +++ b/lib/modules/manager/gradle-wrapper/utils.ts @@ -28,7 +28,7 @@ export async function prepareGradleCommand( if (gradlewStat?.isFile() === true) { // if the file is not executable by others if (os.platform() !== 'win32' && (gradlewStat.mode & 0o1) === 0) { - logger.warn('Gradle wrapper is missing the executable bit'); + logger.debug('Gradle wrapper is missing the executable bit'); // add the execution permission to the owner, group and others await chmodLocalFile(gradlewFile, gradlewStat.mode | 0o111); } diff --git a/lib/modules/manager/gradle/extract/consistent-versions-plugin.spec.ts b/lib/modules/manager/gradle/extract/consistent-versions-plugin.spec.ts index c5e89a4dd9f50a..dcf567152bf501 100644 --- a/lib/modules/manager/gradle/extract/consistent-versions-plugin.spec.ts +++ b/lib/modules/manager/gradle/extract/consistent-versions-plugin.spec.ts @@ -20,6 +20,18 @@ describe('modules/manager/gradle/extract/consistent-versions-plugin', () => { expect(usesGcv('othersub/versions.props', fsMock)).toBeFalse(); }); + it('detects lock file header introduced with gradle-consistent-versions version 2.20.0', () => { + const fsMock = { + 'build.gradle.kts': `(this file contains) 'com.palantir.consistent-versions'`, + 'versions.props': `org.apache.lucene:* = 1.2.3`, + 'versions.lock': stripIndent` + # Run ./gradlew writeVersionsLock to regenerate this file + org.apache.lucene:lucene-core:1.2.3`, + }; + + expect(usesGcv('versions.props', fsMock)).toBeTrue(); + }); + it('gradle-consistent-versions plugin correct position for CRLF and LF', () => { const crlfProps = parsePropsFile(`a.b:c.d=1\r\na.b:c.e=2`); expect(crlfProps).toBeArrayOfSize(2); @@ -47,7 +59,6 @@ describe('modules/manager/gradle/extract/consistent-versions-plugin', () => { expect(parsedProps[0]).toMatchObject({ size: 1 }); // no 7 is valid exact dep expect(parsedProps[1]).toMatchObject({ size: 1 }); // no 8 is valid glob dep - // lockfile const parsedLock = parseLockFile(stripIndent` # comment:foo.bar:1 (10 constraints: 95be0c15) 123.foo:bar:2 (10 constraints: 95be0c15) diff --git a/lib/modules/manager/gradle/extract/consistent-versions-plugin.ts b/lib/modules/manager/gradle/extract/consistent-versions-plugin.ts index 8a1d602a2d1ff1..032fa1c086c8fa 100644 --- a/lib/modules/manager/gradle/extract/consistent-versions-plugin.ts +++ b/lib/modules/manager/gradle/extract/consistent-versions-plugin.ts @@ -8,8 +8,9 @@ import { isDependencyString, versionLikeSubstring } from '../utils'; export const VERSIONS_PROPS = 'versions.props'; export const VERSIONS_LOCK = 'versions.lock'; -const LOCKFILE_HEADER_TEXT = - '# Run ./gradlew --write-locks to regenerate this file'; +export const LOCKFIlE_HEADER_TEXT = regEx( + /^# Run \.\/gradlew (?:--write-locks|writeVersionsLock) to regenerate this file/, +); /** * Determines if Palantir gradle-consistent-versions is in use, https://github.com/palantir/gradle-consistent-versions. @@ -26,9 +27,8 @@ export function usesGcv( versionsPropsFilename, VERSIONS_LOCK, ); - return ( - fileContents[versionsLockFile]?.startsWith(LOCKFILE_HEADER_TEXT) ?? false - ); + + return !!fileContents[versionsLockFile]?.match(LOCKFIlE_HEADER_TEXT); } /** diff --git a/lib/modules/manager/gradle/parser.spec.ts b/lib/modules/manager/gradle/parser.spec.ts index e120db42dbb08d..101848c6c81937 100644 --- a/lib/modules/manager/gradle/parser.spec.ts +++ b/lib/modules/manager/gradle/parser.spec.ts @@ -157,10 +157,15 @@ describe('modules/manager/gradle/parser', () => { it('map with interpolated dependency strings', () => { const input = codeBlock` def slfj4Version = "2.0.0" + def lifecycle_version = "2.5.1" libraries = [ jcl: "org.slf4j:jcl-over-slf4j:\${slfj4Version}", releaseCoroutines: "org.jetbrains.kotlinx:kotlinx-coroutines-core:0.26.1-eap13" api: "org.slf4j:slf4j-api:$slfj4Version", + lifecycle: [ + "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version", + "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" + ] ] foo = [ group: "org.slf4j", name: "slf4j-ext", version: slfj4Version ] `; @@ -182,6 +187,16 @@ describe('modules/manager/gradle/parser', () => { groupName: 'slfj4Version', currentValue: '2.0.0', }, + { + depName: 'androidx.lifecycle:lifecycle-runtime-ktx', + groupName: 'lifecycle_version', + currentValue: '2.5.1', + }, + { + depName: 'androidx.lifecycle:lifecycle-viewmodel-ktx', + groupName: 'lifecycle_version', + currentValue: '2.5.1', + }, { depName: 'org.slf4j:slf4j-ext', groupName: 'slfj4Version', diff --git a/lib/modules/manager/gradle/parser/assignments.ts b/lib/modules/manager/gradle/parser/assignments.ts index 2f6e9260305e23..08114ff856f030 100644 --- a/lib/modules/manager/gradle/parser/assignments.ts +++ b/lib/modules/manager/gradle/parser/assignments.ts @@ -13,7 +13,10 @@ import { storeInTokenMap, storeVarToken, } from './common'; -import { qGroovyMapNotationDependencies } from './dependencies'; +import { + qDependencyStrings, + qGroovyMapNotationDependencies, +} from './dependencies'; import { handleAssignment } from './handlers'; // foo = "1.2.3" @@ -79,6 +82,8 @@ const qGroovySingleMapOfVarAssignment = q.alt( .join(qValueMatcher) .handler((ctx) => storeInTokenMap(ctx, 'valToken')) .handler(handleAssignment), + // ["foo:bar:1.2.3", "foo:baz:$qux"] + qDependencyStrings, ); const qGroovyMapOfExpr = ( diff --git a/lib/modules/manager/helm-requirements/readme.md b/lib/modules/manager/helm-requirements/readme.md index 3099144f3c3b2e..54cbc289ea6eb6 100644 --- a/lib/modules/manager/helm-requirements/readme.md +++ b/lib/modules/manager/helm-requirements/readme.md @@ -12,6 +12,6 @@ The `helm-requirements` manager defines this default registryAlias: If your Helm charts make use of repository aliases then you will need to configure an `registryAliases` object in your config to tell Renovate where to look for them. Be aware that alias values must be properly formatted URIs. -If you need to change the versioning format, read the [versioning](../../versioning.md) documentation to learn more. +If you need to change the versioning format, read the [versioning](../../versioning/index.md) documentation to learn more. To learn how to use Helm with private packages, read [private package support, Package Manager Credentials for Artifact Updating, helm](../../../getting-started/private-packages.md#helm). diff --git a/lib/modules/manager/helm-values/extract.spec.ts b/lib/modules/manager/helm-values/extract.spec.ts index d8eb805db86f7a..abab66d76f44e0 100644 --- a/lib/modules/manager/helm-values/extract.spec.ts +++ b/lib/modules/manager/helm-values/extract.spec.ts @@ -1,4 +1,6 @@ import { Fixtures } from '../../../../test/fixtures'; +import { partial } from '../../../../test/util'; +import type { ExtractConfig } from '../types'; import { extractPackageFile } from '.'; const helmDefaultChartInitValues = Fixtures.get( @@ -9,20 +11,34 @@ const helmMultiAndNestedImageValues = Fixtures.get( 'multi_and_nested_image_values.yaml', ); +const config = partial({}); + +const configAliases = partial({ + registryAliases: { + 'quay.io': 'registry.internal/mirror/quay.io', + }, +}); + +const packageFile = 'values.yaml'; + describe('modules/manager/helm-values/extract', () => { describe('extractPackageFile()', () => { it('returns null for invalid yaml file content', () => { - const result = extractPackageFile('nothing here: ['); + const result = extractPackageFile('nothing here: [', packageFile, config); expect(result).toBeNull(); }); it('returns null for empty yaml file content', () => { - const result = extractPackageFile(''); + const result = extractPackageFile('', packageFile, config); expect(result).toBeNull(); }); it('extracts from values.yaml correctly with same structure as "helm create"', () => { - const result = extractPackageFile(helmDefaultChartInitValues); + const result = extractPackageFile( + helmDefaultChartInitValues, + packageFile, + config, + ); expect(result).toMatchSnapshot({ deps: [ { @@ -34,7 +50,11 @@ describe('modules/manager/helm-values/extract', () => { }); it('extracts from complex values file correctly"', () => { - const result = extractPackageFile(helmMultiAndNestedImageValues); + const result = extractPackageFile( + helmMultiAndNestedImageValues, + packageFile, + config, + ); expect(result).toMatchSnapshot(); expect(result?.deps).toHaveLength(5); }); @@ -43,7 +63,7 @@ describe('modules/manager/helm-values/extract', () => { const multiDocumentFile = Fixtures.get( 'single_file_with_multiple_documents.yaml', ); - const result = extractPackageFile(multiDocumentFile); + const result = extractPackageFile(multiDocumentFile, packageFile, config); expect(result).toMatchObject({ deps: [ { @@ -61,5 +81,32 @@ describe('modules/manager/helm-values/extract', () => { ], }); }); + + it('extract data from file with registry aliases', () => { + const multiDocumentFile = Fixtures.get( + 'single_file_with_multiple_documents.yaml', + ); + const result = extractPackageFile( + multiDocumentFile, + packageFile, + configAliases, + ); + expect(result).toMatchObject({ + deps: [ + { + currentValue: 'v0.13.10', + depName: 'registry.internal/mirror/quay.io/metallb/controller', + datasource: 'docker', + versioning: 'docker', + }, + { + currentValue: 'v0.13.10', + depName: 'registry.internal/mirror/quay.io/metallb/speaker', + datasource: 'docker', + versioning: 'docker', + }, + ], + }); + }); }); }); diff --git a/lib/modules/manager/helm-values/extract.ts b/lib/modules/manager/helm-values/extract.ts index 61437fdbb79571..f4041420dd4445 100644 --- a/lib/modules/manager/helm-values/extract.ts +++ b/lib/modules/manager/helm-values/extract.ts @@ -2,23 +2,28 @@ import { logger } from '../../../logger'; import { parseYaml } from '../../../util/yaml'; import { id as dockerVersioning } from '../../versioning/docker'; import { getDep } from '../dockerfile/extract'; -import type { PackageDependency, PackageFileContent } from '../types'; +import type { + ExtractConfig, + PackageDependency, + PackageFileContent, +} from '../types'; import type { HelmDockerImageDependency } from './types'; import { matchesHelmValuesDockerHeuristic, matchesHelmValuesInlineImage, } from './util'; -function getHelmDep({ - registry, - repository, - tag, -}: { - registry: string; - repository: string; - tag: string; -}): PackageDependency { - const dep = getDep(`${registry}${repository}:${tag}`, false); +function getHelmDep( + registry: string, + repository: string, + tag: string, + config: ExtractConfig, +): PackageDependency { + const dep = getDep( + `${registry}${repository}:${tag}`, + false, + config.registryAliases, + ); dep.replaceString = tag; dep.versioning = dockerVersioning; dep.autoReplaceStringTemplate = @@ -34,6 +39,7 @@ function getHelmDep({ function findDependencies( parsedContent: Record | HelmDockerImageDependency, packageDependencies: Array, + config: ExtractConfig, ): Array { if (!parsedContent || typeof parsedContent !== 'object') { return packageDependencies; @@ -47,11 +53,15 @@ function findDependencies( registry = registry ? `${registry}/` : ''; const repository = String(currentItem.repository); const tag = `${currentItem.tag ?? currentItem.version}`; - packageDependencies.push(getHelmDep({ repository, tag, registry })); + packageDependencies.push(getHelmDep(registry, repository, tag, config)); } else if (matchesHelmValuesInlineImage(key, value)) { - packageDependencies.push(getDep(value)); + packageDependencies.push(getDep(value, true, config.registryAliases)); } else { - findDependencies(value as Record, packageDependencies); + findDependencies( + value as Record, + packageDependencies, + config, + ); } }); return packageDependencies; @@ -59,7 +69,8 @@ function findDependencies( export function extractPackageFile( content: string, - packageFile?: string, + packageFile: string, + config: ExtractConfig, ): PackageFileContent | null { let parsedContent: Record[] | HelmDockerImageDependency[]; try { @@ -75,7 +86,7 @@ export function extractPackageFile( const deps: PackageDependency>[] = []; for (const con of parsedContent) { - deps.push(...findDependencies(con, [])); + deps.push(...findDependencies(con, [], config)); } if (deps.length) { diff --git a/lib/modules/manager/helm-values/readme.md b/lib/modules/manager/helm-values/readme.md index 3d35070af11e96..3c8ee29ddcdf64 100644 --- a/lib/modules/manager/helm-values/readme.md +++ b/lib/modules/manager/helm-values/readme.md @@ -17,4 +17,4 @@ coreImage: tag: 2.1.3-debian-10-r38 ``` -If you need to change the versioning format, read the [versioning](../../versioning.md) documentation to learn more. +If you need to change the versioning format, read the [versioning](../../versioning/index.md) documentation to learn more. diff --git a/lib/modules/manager/helmfile/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/helmfile/__snapshots__/extract.spec.ts.snap index a81b5d69302566..6f6a40faac0ed3 100644 --- a/lib/modules/manager/helmfile/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/helmfile/__snapshots__/extract.spec.ts.snap @@ -1,49 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`modules/manager/helmfile/extract extractPackageFile() parses multidoc yaml 1`] = ` -{ - "datasource": "helm", - "deps": [ - { - "depName": "manifests", - "skipReason": "local-chart", - }, - { - "currentValue": "7.4.3", - "depName": "rabbitmq", - "registryUrls": [ - "https://charts.bitnami.com/bitnami", - ], - }, - { - "currentValue": "13.7", - "depName": "kube-prometheus-stack", - "registryUrls": [ - "https://prometheus-community.github.io/helm-charts", - ], - }, - { - "depName": "invalid", - "skipReason": "invalid-name", - }, - { - "depName": "external-dns", - "skipReason": "invalid-version", - }, - { - "currentValue": "0.1.0", - "depName": "raw", - "registryUrls": [ - "https://charts.helm.sh/incubator/", - ], - }, - ], - "managerData": { - "needKustomize": true, - }, -} -`; - exports[`modules/manager/helmfile/extract extractPackageFile() skip chart that does not have specified version 1`] = ` { "datasource": "helm", diff --git a/lib/modules/manager/helmfile/extract.spec.ts b/lib/modules/manager/helmfile/extract.spec.ts index 315412754bccca..e2378baf3c38cc 100644 --- a/lib/modules/manager/helmfile/extract.spec.ts +++ b/lib/modules/manager/helmfile/extract.spec.ts @@ -212,15 +212,28 @@ describe('modules/manager/helmfile/extract', () => { }, }, ); - expect(result).toMatchSnapshot({ + expect(result).toMatchObject({ datasource: 'helm', deps: [ { depName: 'manifests', skipReason: 'local-chart' }, - { depName: 'rabbitmq', currentValue: '7.4.3' }, - { depName: 'kube-prometheus-stack', currentValue: '13.7' }, - { depName: 'invalid', skipReason: 'invalid-name' }, + { + depName: 'rabbitmq', + currentValue: '7.4.3', + registryUrls: ['https://charts.bitnami.com/bitnami'], + }, + { + depName: 'kube-prometheus-stack', + currentValue: '13.7', + registryUrls: [ + 'https://prometheus-community.github.io/helm-charts', + ], + }, { depName: 'external-dns', skipReason: 'invalid-version' }, - { depName: 'raw' }, + { + depName: 'raw', + currentValue: '0.1.0', + registryUrls: ['https://charts.helm.sh/incubator/'], + }, ], managerData: { needKustomize: true }, }); @@ -292,9 +305,6 @@ describe('modules/manager/helmfile/extract', () => { { skipReason: 'invalid-version', }, - { - skipReason: 'invalid-name', - }, { currentValue: '1.0.0', depName: 'example', @@ -380,10 +390,6 @@ describe('modules/manager/helmfile/extract', () => { depName: '', skipReason: 'local-chart', }, - { - depName: null, - skipReason: 'local-chart', - }, { depName: 'ingress-nginx', currentValue: '3.37.0', @@ -401,7 +407,6 @@ describe('modules/manager/helmfile/extract', () => { registryUrls: ['https://charts.helm.sh/stable'], }, { depName: 'kube-prometheus-stack', skipReason: 'invalid-version' }, - { depName: 'example-external', skipReason: 'invalid-name' }, { depName: 'external-dns', currentValue: '2.0.0', diff --git a/lib/modules/manager/helmfile/extract.ts b/lib/modules/manager/helmfile/extract.ts index fa34d53900d81a..83878a20236f05 100644 --- a/lib/modules/manager/helmfile/extract.ts +++ b/lib/modules/manager/helmfile/extract.ts @@ -1,15 +1,18 @@ import is from '@sindresorhus/is'; import { logger } from '../../../logger'; +import { coerceArray } from '../../../util/array'; import { regEx } from '../../../util/regex'; import { parseYaml } from '../../../util/yaml'; import { DockerDatasource } from '../../datasource/docker'; import { HelmDatasource } from '../../datasource/helm'; +import { isOCIRegistry } from '../helmv3/oci'; import type { ExtractConfig, PackageDependency, PackageFileContent, } from '../types'; import type { Doc } from './schema'; +import { Doc as documentSchema } from './schema'; import { kustomizationsKeysUsed, localChartHasKustomizationsYaml, @@ -24,10 +27,6 @@ function isLocalPath(possiblePath: string): boolean { ); } -function isOciUrl(possibleUrl: string): boolean { - return possibleUrl.startsWith('oci://'); -} - export async function extractPackageFile( content: string, packageFile: string, @@ -39,8 +38,9 @@ export async function extractPackageFile( // Record kustomization usage for all deps, since updating artifacts is run on the helmfile.yaml as a whole. let needKustomize = false; try { - // TODO: use schema (#9610) docs = parseYaml(content, null, { + customSchema: documentSchema, + failureBehaviour: 'filter', removeTemplates: true, json: true, }); @@ -52,10 +52,6 @@ export async function extractPackageFile( return null; } for (const doc of docs) { - if (!doc) { - continue; - } - // Always check for repositories in the current document and override the existing ones if any (as YAML does) if (doc.repositories) { registryAliases = {}; @@ -68,23 +64,10 @@ export async function extractPackageFile( ); } - // Skip extraction if the document contains no releases - if (!is.array(doc.releases)) { - continue; - } - - for (const dep of doc.releases) { + for (const dep of coerceArray(doc.releases)) { let depName = dep.chart; let repoName: string | null = null; - if (!is.string(dep.chart)) { - deps.push({ - depName: dep.name, - skipReason: 'invalid-name', - }); - continue; - } - // If it starts with ./ ../ or / then it's a local path if (isLocalPath(dep.chart)) { if ( @@ -100,11 +83,7 @@ export async function extractPackageFile( continue; } - if (is.number(dep.version)) { - dep.version = String(dep.version); - } - - if (isOciUrl(dep.chart)) { + if (isOCIRegistry(dep.chart)) { const v = dep.chart.substring(6).split('/'); depName = v.pop()!; repoName = v.join('/'); @@ -138,7 +117,7 @@ export async function extractPackageFile( const repository = doc.repositories?.find( (repo) => repo.name === repoName, ); - if (isOciUrl(dep.chart)) { + if (isOCIRegistry(dep.chart)) { res.datasource = DockerDatasource.id; res.packageName = repoName + '/' + depName; } else if (repository?.oci) { diff --git a/lib/modules/manager/helmfile/readme.md b/lib/modules/manager/helmfile/readme.md index deaca872ab349b..692f768d15b47e 100644 --- a/lib/modules/manager/helmfile/readme.md +++ b/lib/modules/manager/helmfile/readme.md @@ -12,7 +12,7 @@ The `helmfile` manager defines this default registryAlias: If your Helm charts make use of repository aliases then you will need to configure an `registryAliases` object in your config to tell Renovate where to look for them. Be aware that alias values must be properly formatted URIs. -If you need to change the versioning format, read the [versioning](../../versioning.md) documentation to learn more. +If you need to change the versioning format, read the [versioning](../../versioning/index.md) documentation to learn more. ### Private repositories and registries diff --git a/lib/modules/manager/helmfile/schema.ts b/lib/modules/manager/helmfile/schema.ts index a885e6265e7d94..abfefc194ab169 100644 --- a/lib/modules/manager/helmfile/schema.ts +++ b/lib/modules/manager/helmfile/schema.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { Yaml } from '../../../util/schema-utils'; +import { LooseArray, Yaml } from '../../../util/schema-utils'; export const HelmRepository = z.object({ name: z.string(), @@ -11,7 +11,12 @@ export type HelmRepository = z.infer; export const HelmRelease = z.object({ name: z.string(), chart: z.string(), - version: z.string(), + version: z + .string() + .or(z.number()) + .optional() + .nullable() + .transform((version) => (version ? version.toString() : null)), strategicMergePatches: z.unknown().optional(), jsonPatches: z.unknown().optional(), transformers: z.unknown().optional(), @@ -19,8 +24,8 @@ export const HelmRelease = z.object({ export type HelmRelease = z.infer; export const Doc = z.object({ - releases: z.array(HelmRelease).optional(), - repositories: z.array(HelmRepository).optional(), + releases: LooseArray(HelmRelease).optional(), + repositories: LooseArray(HelmRepository).optional(), }); export type Doc = z.infer; diff --git a/lib/modules/manager/helmsman/extract.ts b/lib/modules/manager/helmsman/extract.ts index 63147845212486..786876a2403219 100644 --- a/lib/modules/manager/helmsman/extract.ts +++ b/lib/modules/manager/helmsman/extract.ts @@ -4,6 +4,7 @@ import { regEx } from '../../../util/regex'; import { parseSingleYaml } from '../../../util/yaml'; import { DockerDatasource } from '../../datasource/docker'; import { HelmDatasource } from '../../datasource/helm'; +import { isOCIRegistry, removeOCIPrefix } from '../helmv3/oci'; import type { ExtractConfig, PackageDependency, @@ -33,10 +34,9 @@ function createDep( dep.currentValue = anApp.version; // in case of OCI repository, we need a PackageDependency with a DockerDatasource and a packageName - const isOci = anApp.chart?.startsWith('oci://'); - if (isOci) { + if (isOCIRegistry(anApp.chart)) { dep.datasource = DockerDatasource.id; - dep.packageName = anApp.chart!.replace('oci://', ''); + dep.packageName = removeOCIPrefix(anApp.chart!); return dep; } diff --git a/lib/modules/manager/helmv3/artifacts.ts b/lib/modules/manager/helmv3/artifacts.ts index f801d41fffc4be..6d69751f823f39 100644 --- a/lib/modules/manager/helmv3/artifacts.ts +++ b/lib/modules/manager/helmv3/artifacts.ts @@ -18,12 +18,12 @@ import { DockerDatasource } from '../../datasource/docker'; import { HelmDatasource } from '../../datasource/helm'; import type { UpdateArtifact, UpdateArtifactsResult } from '../types'; import { generateHelmEnvs, generateLoginCmd } from './common'; +import { isOCIRegistry, removeOCIPrefix } from './oci'; import type { ChartDefinition, Repository, RepositoryRule } from './types'; import { aliasRecordToRepositories, getRepositories, isFileInDir, - isOCIRegistry, } from './utils'; async function helmCommands( @@ -38,7 +38,7 @@ async function helmCommands( .map((value) => { return { ...value, - repository: value.repository.replace('oci://', ''), + repository: removeOCIPrefix(value.repository), hostRule: hostRules.find({ url: value.repository.replace('oci://', 'https://'), //TODO we need to replace this, as oci:// will not be accepted as protocol hostType: DockerDatasource.id, diff --git a/lib/modules/manager/helmv3/oci.ts b/lib/modules/manager/helmv3/oci.ts new file mode 100644 index 00000000000000..31e611b19cba00 --- /dev/null +++ b/lib/modules/manager/helmv3/oci.ts @@ -0,0 +1,19 @@ +import is from '@sindresorhus/is'; +import type { Repository } from './types'; + +export function isOCIRegistry( + repository: Repository | string | null | undefined, +): boolean { + if (is.nullOrUndefined(repository)) { + return false; + } + const repo = is.string(repository) ? repository : repository.repository; + return repo.startsWith('oci://'); +} + +export function removeOCIPrefix(repository: string): string { + if (isOCIRegistry(repository)) { + return repository.replace('oci://', ''); + } + return repository; +} diff --git a/lib/modules/manager/helmv3/readme.md b/lib/modules/manager/helmv3/readme.md index d985965795be99..af2f5017dd9b8f 100644 --- a/lib/modules/manager/helmv3/readme.md +++ b/lib/modules/manager/helmv3/readme.md @@ -16,7 +16,7 @@ The `helmv3` manager defines this default registryAlias: If you use repository aliases in your Helm charts then you must set an `registryAliases` object in your configuration file so Renovate knows where to find the repository. Alias values must be properly formatted URIs. -If you need to change the versioning format, read our [versioning](../../versioning.md) documentation to learn more. +If you need to change the versioning format, read our [versioning](../../versioning/index.md) documentation to learn more. ### Private repositories and registries diff --git a/lib/modules/manager/helmv3/utils.spec.ts b/lib/modules/manager/helmv3/utils.spec.ts index fd207a6aa34747..cc4e793ab58e51 100644 --- a/lib/modules/manager/helmv3/utils.spec.ts +++ b/lib/modules/manager/helmv3/utils.spec.ts @@ -1,4 +1,5 @@ -import { isAlias, isOCIRegistry, resolveAlias } from './utils'; +import { isOCIRegistry } from './oci'; +import { isAlias, resolveAlias } from './utils'; describe('modules/manager/helmv3/utils', () => { describe('.resolveAlias()', () => { diff --git a/lib/modules/manager/helmv3/utils.ts b/lib/modules/manager/helmv3/utils.ts index a659eba6fcfd99..3edf8164d6a95d 100644 --- a/lib/modules/manager/helmv3/utils.ts +++ b/lib/modules/manager/helmv3/utils.ts @@ -1,8 +1,8 @@ -import is from '@sindresorhus/is'; import upath from 'upath'; import { logger } from '../../../logger'; import { DockerDatasource } from '../../datasource/docker'; import type { PackageDependency } from '../types'; +import { removeOCIPrefix } from './oci'; import type { ChartDefinition, Repository } from './types'; export function parseRepository( @@ -16,7 +16,7 @@ export function parseRepository( switch (url.protocol) { case 'oci:': res.datasource = DockerDatasource.id; - res.packageName = `${repositoryURL.replace('oci://', '')}/${depName}`; + res.packageName = `${removeOCIPrefix(repositoryURL)}/${depName}`; // https://github.com/helm/helm/issues/10312 // https://github.com/helm/helm/issues/10678 res.pinDigests = false; @@ -86,16 +86,6 @@ export function isAlias(repository: string): boolean { return repository.startsWith('@') || repository.startsWith('alias:'); } -export function isOCIRegistry( - repository: Repository | string | null | undefined, -): boolean { - if (is.nullOrUndefined(repository)) { - return false; - } - const repo = is.string(repository) ? repository : repository.repository; - return repo.startsWith('oci://'); -} - export function aliasRecordToRepositories( registryAliases: Record, ): Repository[] { diff --git a/lib/modules/manager/homebrew/__fixtures__/ibazel.rb b/lib/modules/manager/homebrew/__fixtures__/ibazel.rb index edac288dbf4d1b..9f183d6d010543 100644 --- a/lib/modules/manager/homebrew/__fixtures__/ibazel.rb +++ b/lib/modules/manager/homebrew/__fixtures__/ibazel.rb @@ -13,17 +13,17 @@ # limitations under the License. =begin - url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + url "https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz" sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' =end -# url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" +# url "https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz" # sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' $sha256 = '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4'; class Ibazel < Formula desc 'IBazel is a tool for building Bazel targets when source files change.' homepage 'https://github.com/bazelbuild/bazel-watcher' - url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + url "https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz" # To generate run: # curl https://codeload.github.com/bazelbuild/bazel-watcher/tar.gz/v0.8.2 | sha256sum diff --git a/lib/modules/manager/homebrew/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/homebrew/__snapshots__/extract.spec.ts.snap index bf6d3ccb486baf..d01bbdadad0cd5 100644 --- a/lib/modules/manager/homebrew/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/homebrew/__snapshots__/extract.spec.ts.snap @@ -11,7 +11,7 @@ exports[`modules/manager/homebrew/extract extractPackageFile() extracts "archive "ownerName": "bazelbuild", "repoName": "bazel-watcher", "sha256": "26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4", - "url": "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz", + "url": "https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz", }, }, ], @@ -37,6 +37,24 @@ exports[`modules/manager/homebrew/extract extractPackageFile() extracts "release `; exports[`modules/manager/homebrew/extract extractPackageFile() handles no space before class header 1`] = ` +{ + "deps": [ + { + "currentValue": "v0.8.2", + "datasource": "github-tags", + "depName": "bazelbuild/bazel-watcher", + "managerData": { + "ownerName": "bazelbuild", + "repoName": "bazel-watcher", + "sha256": "26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4", + "url": "https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz", + }, + }, + ], +} +`; + +exports[`modules/manager/homebrew/extract extractPackageFile() handles old "archive" github url format 1`] = ` { "deps": [ { @@ -122,7 +140,7 @@ exports[`modules/manager/homebrew/extract extractPackageFile() skips if sha256 f "ownerName": "bazelbuild", "repoName": "bazel-watcher", "sha256": "26f5125218fad2741d3caf937b0229", - "url": "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz", + "url": "https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz", }, "skipReason": "invalid-sha256", }, @@ -141,7 +159,7 @@ exports[`modules/manager/homebrew/extract extractPackageFile() skips if there is "ownerName": "bazelbuild", "repoName": "bazel-watcher", "sha256": null, - "url": "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz", + "url": "https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz", }, "skipReason": "invalid-sha256", }, diff --git a/lib/modules/manager/homebrew/__snapshots__/update.spec.ts.snap b/lib/modules/manager/homebrew/__snapshots__/update.spec.ts.snap index 28d91ff1307ff9..139ef7b0f45830 100644 --- a/lib/modules/manager/homebrew/__snapshots__/update.spec.ts.snap +++ b/lib/modules/manager/homebrew/__snapshots__/update.spec.ts.snap @@ -79,10 +79,10 @@ exports[`modules/manager/homebrew/update updates "archive" github dependency 1`] # limitations under the License. =begin - url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + url "https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz" sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' =end -# url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" +# url "https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz" # sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' $sha256 = '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4'; @@ -114,6 +114,57 @@ end " `; +exports[`modules/manager/homebrew/update updates "archive" github dependency from old url format 1`] = ` +"# Copyright 2018 The Bazel Authors. All rights reserved. +# +# 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. + +=begin + url "https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz" + sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' +=end +# url "https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz" +# sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' + +$sha256 = '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4'; +class Ibazel < Formula + desc 'IBazel is a tool for building Bazel targets when source files change.' + homepage 'https://github.com/bazelbuild/bazel-watcher' + url "https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz" + + # To generate run: + # curl https://codeload.github.com/bazelbuild/bazel-watcher/tar.gz/v0.8.2 | sha256sum + sha256 '2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae' + + bottle :unneeded + + depends_on "bazelbuild/tap/bazel" => :build + + def install + system 'bazel', 'build', '--config=release', '--verbose_failures', '--experimental_platforms=@io_bazel_rules_go//go/toolchain:darwin_amd64', '//ibazel:ibazel' + bin.install 'bazel-bin/ibazel/darwin_amd64_pure_stripped/ibazel' => 'ibazel' + end + + test do + # Since ibazel loops in most cases the quickest check of valididty + # I can think of is to get the version output which happens when + # invoked without any arguments. + system bin / 'ibazel' + end +end +" +`; + exports[`modules/manager/homebrew/update updates "releases" github dependency 1`] = ` "=begin url "https://github.com/aide/aide/releases/download/v0.16.1/aide-0.16.1.tar.gz" diff --git a/lib/modules/manager/homebrew/extract.spec.ts b/lib/modules/manager/homebrew/extract.spec.ts index 6ef01fa7578390..0a770eb30aa14a 100644 --- a/lib/modules/manager/homebrew/extract.spec.ts +++ b/lib/modules/manager/homebrew/extract.spec.ts @@ -44,7 +44,7 @@ describe('modules/manager/homebrew/extract', () => { expect(res).toMatchSnapshot(); }); - it('handles no space before class header', () => { + it('handles old "archive" github url format', () => { const content = `class Ibazel < Formula desc 'IBazel is a tool for building Bazel targets when source files change.' homepage 'https://github.com/bazelbuild/bazel-watcher' @@ -58,12 +58,26 @@ describe('modules/manager/homebrew/extract', () => { expect(res).toMatchSnapshot(); }); + it('handles no space before class header', () => { + const content = `class Ibazel < Formula + desc 'IBazel is a tool for building Bazel targets when source files change.' + homepage 'https://github.com/bazelbuild/bazel-watcher' + url "https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz" + sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' + end + `; + const res = extractPackageFile(content); + expect(res).not.toBeNull(); + expect(res?.deps[0].skipReason).toBeUndefined(); + expect(res).toMatchSnapshot(); + }); + it('returns null for invalid class header 1', () => { const content = ` class Ibazel !?# Formula desc 'IBazel is a tool for building Bazel targets when source files change.' homepage 'https://github.com/bazelbuild/bazel-watcher' - url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + url "https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz" sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' end `; @@ -75,7 +89,7 @@ describe('modules/manager/homebrew/extract', () => { class Ibazel < NotFormula desc 'IBazel is a tool for building Bazel targets when source files change.' homepage 'https://github.com/bazelbuild/bazel-watcher' - url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + url "https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz" sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' end `; @@ -87,7 +101,7 @@ describe('modules/manager/homebrew/extract', () => { class Ibazel < Formula desc 'IBazel is a tool for building Bazel targets when source files change.' homepage 'https://github.com/bazelbuild/bazel-watcher' - not_url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + not_url "https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz" sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' end `; @@ -102,7 +116,7 @@ describe('modules/manager/homebrew/extract', () => { class Ibazel < Formula desc 'IBazel is a tool for building Bazel targets when source files change.' homepage 'https://github.com/bazelbuild/bazel-watcher' - url ??https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + url ??https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz" sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' end `; @@ -132,7 +146,7 @@ describe('modules/manager/homebrew/extract', () => { class Ibazel < Formula desc 'IBazel is a tool for building Bazel targets when source files change.' homepage 'https://github.com/bazelbuild/bazel-watcher' - url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + url "https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz" not_sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' end `; @@ -147,7 +161,7 @@ describe('modules/manager/homebrew/extract', () => { class Ibazel < Formula desc 'IBazel is a tool for building Bazel targets when source files change.' homepage 'https://github.com/bazelbuild/bazel-watcher' - url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + url "https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz" sha256 '26f5125218fad2741d3caf937b0229' end `; diff --git a/lib/modules/manager/homebrew/extract.ts b/lib/modules/manager/homebrew/extract.ts index 2aabaacdab5b0b..f9442bce2a6068 100644 --- a/lib/modules/manager/homebrew/extract.ts +++ b/lib/modules/manager/homebrew/extract.ts @@ -73,7 +73,12 @@ export function parseUrlPath( const repoName = s[1]; let currentValue: string | undefined; if (s[2] === 'archive') { + // old archive url in form: [...]/archive/.tar.gz currentValue = s[3]; + if (currentValue === 'refs') { + // new archive url in form: [...]/archive/refs/tags/.tar.gz + currentValue = s[5]; + } const targz = currentValue.slice( currentValue.length - 7, currentValue.length, diff --git a/lib/modules/manager/homebrew/update.spec.ts b/lib/modules/manager/homebrew/update.spec.ts index f169e9977f08b1..2c43f45b4b1a39 100644 --- a/lib/modules/manager/homebrew/update.spec.ts +++ b/lib/modules/manager/homebrew/update.spec.ts @@ -36,6 +36,34 @@ describe('modules/manager/homebrew/update', () => { }); it('updates "archive" github dependency', async () => { + const upgrade = { + currentValue: 'v0.8.2', + depName: 'Ibazel', + managerData: { + ownerName: 'bazelbuild', + repoName: 'bazel-watcher', + sha256: + '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4', + url: 'https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz', + }, + newValue: 'v0.9.3', + }; + httpMock + .scope(baseUrl) + .get( + '/bazelbuild/bazel-watcher/releases/download/v0.9.3/bazel-watcher-0.9.3.tar.gz', + ) + .reply(200, Readable.from(['foo'])); + const newContent = await updateDependency({ + fileContent: ibazel, + upgrade, + }); + expect(newContent).not.toBeNull(); + expect(newContent).not.toBe(ibazel); + expect(newContent).toMatchSnapshot(); + }); + + it('updates "archive" github dependency from old url format', async () => { const upgrade = { currentValue: 'v0.8.2', depName: 'Ibazel', @@ -72,7 +100,7 @@ describe('modules/manager/homebrew/update', () => { repoName: 'bazel-watcher', sha256: '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4', - url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz', + url: 'https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz', }, newValue: 'v0.9.3', }; @@ -82,7 +110,7 @@ describe('modules/manager/homebrew/update', () => { '/bazelbuild/bazel-watcher/releases/download/v0.9.3/bazel-watcher-0.9.3.tar.gz', ) .replyWithError('') - .get('/bazelbuild/bazel-watcher/archive/v0.9.3.tar.gz') + .get('/bazelbuild/bazel-watcher/archive/refs/tags/v0.9.3.tar.gz') .replyWithError(''); const newContent = await updateDependency({ fileContent: ibazel, @@ -124,7 +152,7 @@ describe('modules/manager/homebrew/update', () => { repoName: 'invalid/repo/name', sha256: '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4', - url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz', + url: 'https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz', }, newValue: 'v0.9.3', }; @@ -134,7 +162,7 @@ describe('modules/manager/homebrew/update', () => { '/bazelbuild/invalid/repo/name/releases/download/v0.9.3/invalid/repo/name-0.9.3.tar.gz', ) .replyWithError('') - .get('/bazelbuild/invalid/repo/name/archive/v0.9.3.tar.gz') + .get('/bazelbuild/invalid/repo/name/archive/refs/tags/v0.9.3.tar.gz') .reply(200, Readable.from(['foo'])); const newContent = await updateDependency({ fileContent: content, @@ -151,21 +179,21 @@ describe('modules/manager/homebrew/update', () => { depName: 'Ibazel', managerData: { ownerName: 'bazelbuild', - repoName: 'wrong-version/archive/v10.2.3.tar.gz', + repoName: 'wrong-version/archive/refs/tags/v10.2.3.tar.gz', sha256: '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4', - url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz', + url: 'https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz', }, newValue: 'v0.9.3', }; httpMock .scope(baseUrl) .get( - '/bazelbuild/wrong-version/archive/v10.2.3.tar.gz/releases/download/v0.9.3/wrong-version/archive/v10.2.3.tar.gz-0.9.3.tar.gz', + '/bazelbuild/wrong-version/archive/refs/tags/v10.2.3.tar.gz/releases/download/v0.9.3/wrong-version/archive/refs/tags/v10.2.3.tar.gz-0.9.3.tar.gz', ) .replyWithError('') .get( - '/bazelbuild/wrong-version/archive/v10.2.3.tar.gz/archive/v0.9.3.tar.gz', + '/bazelbuild/wrong-version/archive/refs/tags/v10.2.3.tar.gz/archive/refs/tags/v0.9.3.tar.gz', ) .reply(200, Readable.from(['foo'])); const newContent = await updateDependency({ @@ -181,7 +209,7 @@ describe('modules/manager/homebrew/update', () => { class Ibazel < Formula desc 'IBazel is a tool for building Bazel targets when source files change.' homepage 'https://github.com/bazelbuild/bazel-watcher' - url ???https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + url ???https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz" sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' end `; @@ -193,7 +221,7 @@ describe('modules/manager/homebrew/update', () => { repoName: 'bazel-watcher', sha256: '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4', - url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz', + url: 'https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz', }, newValue: 'v0.9.3', }; @@ -227,7 +255,7 @@ describe('modules/manager/homebrew/update', () => { repoName: 'bazel-watcher', sha256: '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4', - url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz', + url: 'https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz', }, newValue: 'v0.9.3', }; @@ -250,7 +278,7 @@ describe('modules/manager/homebrew/update', () => { class Ibazel < Formula desc 'IBazel is a tool for building Bazel targets when source files change.' homepage 'https://github.com/bazelbuild/bazel-watcher' - url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + url "https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz" sha256 ???26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' end `; @@ -262,7 +290,7 @@ describe('modules/manager/homebrew/update', () => { repoName: 'bazel-watcher', sha256: '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4', - url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz', + url: 'https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz', }, newValue: 'v0.9.3', }; @@ -285,7 +313,7 @@ describe('modules/manager/homebrew/update', () => { class Ibazel < Formula desc 'IBazel is a tool for building Bazel targets when source files change.' homepage 'https://github.com/bazelbuild/bazel-watcher' - url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + url "https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz" end `; const upgrade = { @@ -296,7 +324,7 @@ describe('modules/manager/homebrew/update', () => { repoName: 'bazel-watcher', sha256: '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4', - url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz', + url: 'https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz', }, newValue: 'v0.9.3', }; @@ -331,7 +359,7 @@ describe('modules/manager/homebrew/update', () => { .scope(baseUrl) .get('/aide/aide/releases/download/v0.17.7/aide-0.17.7.tar.gz') .replyWithError('') - .get('/aide/aide/archive/v0.17.7.tar.gz') + .get('/aide/aide/archive/refs/tags/v0.17.7.tar.gz') .replyWithError(''); const newContent = await updateDependency({ fileContent: aide, diff --git a/lib/modules/manager/homebrew/update.ts b/lib/modules/manager/homebrew/update.ts index 4d5530c8172cf9..a409962fd98d4c 100644 --- a/lib/modules/manager/homebrew/update.ts +++ b/lib/modules/manager/homebrew/update.ts @@ -147,7 +147,7 @@ export async function updateDependency({ */ let newUrl: string; // Example urls: - // "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + // "https://github.com/bazelbuild/bazel-watcher/archive/refs/tags/v0.8.2.tar.gz" // "https://github.com/aide/aide/releases/download/v0.16.1/aide-0.16.1.tar.gz" const oldParsedUrlPath = parseUrlPath(upgrade.managerData?.url); if (!oldParsedUrlPath || !upgrade.managerData) { @@ -171,7 +171,7 @@ export async function updateDependency({ try { const ownerName = String(upgrade.managerData.ownerName); const repoName = String(upgrade.managerData.repoName); - newUrl = `https://github.com/${ownerName}/${repoName}/archive/${upgrade.newValue}.tar.gz`; + newUrl = `https://github.com/${ownerName}/${repoName}/archive/refs/tags/${upgrade.newValue}.tar.gz`; newSha256 = await hashStream(http.stream(newUrl), 'sha256'); } catch (errInner) { logger.debug( diff --git a/lib/modules/manager/index.spec.ts b/lib/modules/manager/index.spec.ts index d5592de414b8ee..1c635b82ff6dfe 100644 --- a/lib/modules/manager/index.spec.ts +++ b/lib/modules/manager/index.spec.ts @@ -59,6 +59,14 @@ describe('modules/manager/index', () => { if (!module.extractPackageFile && !module.extractAllPackageFiles) { return false; } + // managers must export either extractPackageFile or a custom updateDependency function in addition to extractAllPackageFiles + if ( + module.extractAllPackageFiles && + !module.extractPackageFile && + !module.updateDependency + ) { + return false; + } if (Object.values(module).some((v) => v === undefined)) { return false; } diff --git a/lib/modules/manager/kubernetes/__fixtures__/complex.yaml b/lib/modules/manager/kubernetes/__fixtures__/complex.yaml new file mode 100644 index 00000000000000..ed3dfd74f3821b --- /dev/null +++ b/lib/modules/manager/kubernetes/__fixtures__/complex.yaml @@ -0,0 +1,255 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "call-nested" (list . "prosody" "prosody.fullname") }}-common + labels: + {{- include "jitsi-meet.labels" . | nindent 4 }} +data: + ENABLE_AUTH: {{ ternary "1" "0" .Values.enableAuth | quote }} + ENABLE_GUESTS: {{ ternary "1" "0" .Values.enableGuests | quote }} + PUBLIC_URL: {{ include "jitsi-meet.publicURL" . }} + XMPP_DOMAIN: {{ include "jitsi-meet.xmpp.domain" . }} + XMPP_MUC_DOMAIN: {{ .Values.xmpp.mucDomain | default (printf "muc.%s" (include "jitsi-meet.xmpp.domain" .)) }} + XMPP_AUTH_DOMAIN: {{ .Values.xmpp.authDomain | default (printf "auth.%s" (include "jitsi-meet.xmpp.domain" .)) }} + XMPP_GUEST_DOMAIN: {{ .Values.xmpp.guestDomain | default (printf "guest.%s" (include "jitsi-meet.xmpp.domain" .)) }} + XMPP_RECORDER_DOMAIN: {{ .Values.xmpp.recorderDomain | default (printf "recorder.%s" (include "jitsi-meet.xmpp.domain" .)) }} + XMPP_INTERNAL_MUC_DOMAIN: {{ .Values.xmpp.internalMucDomain | default (printf "internal-muc.%s" (include "jitsi-meet.xmpp.domain" .)) }} + {{- if or .Values.websockets.colibri.enabled }} + ENABLE_COLIBRI_WEBSOCKET: 'true' + # TODO: rework into a proper regex or dynamic name list + ENABLE_COLIBRI_WEBSOCKET_UNSAFE_REGEX: '1' + {{- else }} + ENABLE_SCTP: 'true' + ENABLE_COLIBRI_WEBSOCKET: 'false' + JVB_PREFER_SCTP: 'true' + {{- end }} + {{- if .Values.websockets.xmpp.enabled }} + ENABLE_XMPP_WEBSOCKET: 'true' + {{- else }} + ENABLE_XMPP_WEBSOCKET: 'false' + {{- end }} + {{- if .Values.jibri.enabled }} + {{- if .Values.jibri.recording }} + ENABLE_RECORDING: "true" + ENABLE_FILE_RECORDING_SERVICE_SHARING: "true" + {{- end }} + {{- if .Values.jibri.livestreaming }} + ENABLE_LIVESTREAMING: "true" + {{- end }} + {{- end }} + TZ: '{{ .Values.tz }}' + {{- range $key, $value := .Values.extraCommonEnvs }} + {{- if not (kindIs "invalid" $value) }} + {{ $key }}: {{ tpl $value $ | quote }} + {{- end }} + {{- end }} + {{- if .Values.octo.enabled }} + ENABLE_OCTO: "1" + TESTING_OCTO_PROBABILITY: "1" + DEPLOYMENTINFO_REGION: "all" + DEPLOYMENTINFO_USERREGION: "all" + {{- end }} +--- +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "jitsi-meet.web.fullname" . }}-test-connection" + labels: + {{- include "jitsi-meet.web.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "jitsi-meet.web.fullname" . }}:{{ .Values.web.service.port }}'] + restartPolicy: Never +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "jitsi-meet.jvb.fullname" . }} + labels: + {{- include "jitsi-meet.jvb.labels" . | nindent 4 }} + {{- with .Values.jvb.annotations }} + annotations: + {{ toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.jvb.replicaCount }} + selector: + matchLabels: + {{- include "jitsi-meet.jvb.selectorLabels" . | nindent 6 }} + {{- if .Values.jvb.useHostPort }} + strategy: + type: Recreate + {{- end }} + template: + metadata: + labels: + {{- include "jitsi-meet.jvb.selectorLabels" . | nindent 8 }} + {{- range $label, $value := mergeOverwrite .Values.global.podLabels .Values.jvb.podLabels }} + {{ $label }}: {{ $value }} + {{- end }} + annotations: + checksum/config: {{ include (print $.Template.BasePath "/jvb/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/jvb/xmpp-secret.yaml") . | sha256sum }} + {{- if and .Values.jvb.metrics.enabled .Values.jvb.metrics.prometheusAnnotations }} + prometheus.io/port: "9888" + prometheus.io/scrape: "true" + {{- end }} + {{- range $annotation, $value := mergeOverwrite .Values.global.podAnnotations .Values.jvb.podAnnotations }} + {{ $annotation }}: {{ $value|quote }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "jitsi-meet.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.jvb.podSecurityContext | nindent 8 }} + {{- if .Values.jvb.useHostNetwork }} + hostNetwork: true + dnsPolicy: ClusterFirstWithHostNet + {{- end }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.jvb.securityContext | nindent 12 }} + image: "{{ .Values.jvb.image.repository }}:{{ default .Chart.AppVersion .Values.jvb.image.tag }}" + imagePullPolicy: {{ pluck "pullPolicy" .Values.jvb.image .Values.image | first }} + envFrom: + - secretRef: + name: {{ include "call-nested" (list . "prosody" "prosody.fullname") }}-jvb + - configMapRef: + name: {{ include "call-nested" (list . "prosody" "prosody.fullname") }}-common + - configMapRef: + name: {{ include "jitsi-meet.jvb.fullname" . }} + {{- if .Values.global.releaseSecretsOverride.enabled }} + {{- range .Values.global.releaseSecretsOverride.extraEnvFrom }} + - {{ tpl (toYaml . ) $ | indent 12 | trim }} + {{- end }} + {{- end }} + env: + {{- if or .Values.jvb.useNodeIP .Values.jvb.publicIPs }} + - name: DOCKER_HOST_ADDRESS + {{- if .Values.jvb.publicIPs }} + value: {{ first .Values.jvb.publicIPs }} + {{- else }} + valueFrom: + fieldRef: + fieldPath: status.hostIP + {{- end }} + - name: JVB_ADVERTISE_IPS + {{- if .Values.jvb.publicIPs }} + value: {{ .Values.jvb.publicIPs | join "," }} + {{- else }} + valueFrom: + fieldRef: + fieldPath: status.hostIP + {{- end }} + {{- else }} + {{- fail "(jvb.publicIPs | jvb.useNodeIP) Please set an external IP addresses for JVB(s) or enable the Node IP autodetection!" }} + {{- end }} + {{- if .Values.websockets.colibri.enabled }} + - name: JVB_WS_SERVER_ID + valueFrom: + fieldRef: + fieldPath: status.podIP + {{- end }} + {{- if .Values.octo.enabled }} + - name: JVB_OCTO_BIND_ADDRESS + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: JVB_OCTO_RELAY_ID + valueFrom: + fieldRef: + fieldPath: status.podIP + {{- end }} + ports: + - name: rtp-udp + containerPort: {{ .Values.jvb.UDPPort }} + {{- if .Values.jvb.useHostPort }} + hostPort: {{ .Values.jvb.UDPPort }} + {{- end }} + protocol: UDP + {{- if .Values.websockets.colibri.enabled }} + - name: colibri-ws-tcp + containerPort: 9090 + protocol: TCP + {{- end }} + {{- if .Values.octo.enabled }} + - name: octo + containerPort: 4096 + protocol: TCP + {{- end }} + {{- with .Values.jvb.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.jvb.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.jvb.resources | nindent 12 }} + {{- with .Values.jvb.extraVolumeMounts }} + volumeMounts: + {{- toYaml . | nindent 10 }} + {{- end }} + + {{- if .Values.jvb.metrics.enabled }} + - name: metrics + image: {{ .Values.jvb.metrics.image.repository }}:{{ .Values.jvb.metrics.image.tag }} + imagePullPolicy: {{ .Values.jvb.metrics.image.pullPolicy }} + securityContext: + runAsUser: 10001 + command: + - /prometheus-jitsi-meet-exporter + - -videobridge-url + - http://localhost:8080/colibri/stats + ports: + - containerPort: 9888 + name: tcp-metrics + protocol: TCP + readinessProbe: + httpGet: + path: /health + port: 9888 + initialDelaySeconds: 3 + periodSeconds: 5 + resources: + {{- toYaml .Values.jvb.metrics.resources | nindent 12 }} + {{- end }} + + {{- with .Values.jvb.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if or .Values.jvb.useHostPort .Values.jvb.affinity }} + affinity: + {{- if .Values.jvb.affinity }} + {{- toYaml .Values.jvb.affinity | nindent 8 }} + {{- else }} + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app.kubernetes.io/component + operator: In + values: + - jvb + topologyKey: "kubernetes.io/hostname" + {{- end }} + {{- end }} + {{- with .Values.jvb.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.jvb.extraVolumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/lib/modules/manager/kubernetes/__fixtures__/configmap.yaml b/lib/modules/manager/kubernetes/__fixtures__/configmap.yaml index bc9ce8ca7c143c..ce016b7fb9db2d 100644 --- a/lib/modules/manager/kubernetes/__fixtures__/configmap.yaml +++ b/lib/modules/manager/kubernetes/__fixtures__/configmap.yaml @@ -1,3 +1,4 @@ +{- if .Values.configMap.enabled | default false } apiVersion: v1 kind: ConfigMap metadata: @@ -5,3 +6,4 @@ metadata: labels: app: nginx data: {} +{- end } diff --git a/lib/modules/manager/kubernetes/extract.spec.ts b/lib/modules/manager/kubernetes/extract.spec.ts index f1a2d02e83635c..56052789fb6f24 100644 --- a/lib/modules/manager/kubernetes/extract.spec.ts +++ b/lib/modules/manager/kubernetes/extract.spec.ts @@ -164,5 +164,44 @@ kind: ConfigMap ], }); }); + + it('extracts from complex templates', () => { + const res = extractPackageFile( + Fixtures.get('complex.yaml'), + 'complex.yaml', + {}, + ); + expect(res).toEqual({ + deps: [ + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentDigest: undefined, + currentValue: undefined, + datasource: 'docker', + depName: 'busybox', + replaceString: 'busybox', + }, + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentDigest: undefined, + currentValue: undefined, + datasource: 'docker', + depName: '{{', + replaceString: '{{', + }, + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentDigest: undefined, + currentValue: undefined, + datasource: 'docker', + depName: '{{', + replaceString: '{{', + }, + ], + }); + }); }); }); diff --git a/lib/modules/manager/kubernetes/extract.ts b/lib/modules/manager/kubernetes/extract.ts index 3ecad886571adc..f86b1d9f02a619 100644 --- a/lib/modules/manager/kubernetes/extract.ts +++ b/lib/modules/manager/kubernetes/extract.ts @@ -71,7 +71,11 @@ function extractApis( try { // TODO: use schema (#9610) - doc = parseYaml(content); + doc = parseYaml(content, null, { + filename: packageFile, + removeTemplates: true, + json: true, + }); } catch (err) { logger.debug({ err, packageFile }, 'Failed to parse Kubernetes manifest.'); return []; diff --git a/lib/modules/manager/kubernetes/readme.md b/lib/modules/manager/kubernetes/readme.md index 64f6e34c09179f..02269933407cc6 100644 --- a/lib/modules/manager/kubernetes/readme.md +++ b/lib/modules/manager/kubernetes/readme.md @@ -31,4 +31,4 @@ Or if it's only a single file then something like this: } ``` -If you need to change the versioning format, read the [versioning](../../versioning.md) documentation to learn more. +If you need to change the versioning format, read the [versioning](../../versioning/index.md) documentation to learn more. diff --git a/lib/modules/manager/kustomize/__fixtures__/kustomizeHelmChart.yaml b/lib/modules/manager/kustomize/__fixtures__/kustomizeHelmChart.yaml index 56f4887e4ac969..1995c95721ff2d 100644 --- a/lib/modules/manager/kustomize/__fixtures__/kustomizeHelmChart.yaml +++ b/lib/modules/manager/kustomize/__fixtures__/kustomizeHelmChart.yaml @@ -13,3 +13,7 @@ helmCharts: releaseName: moria version: 3.1.3 repo: https://itzg.github.io/minecraft-server-charts +- name: redis + releaseName: redis + version: 18.12.1 + repo: oci://registry-1.docker.io/bitnamicharts diff --git a/lib/modules/manager/kustomize/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/kustomize/__snapshots__/extract.spec.ts.snap index 2d21234e895c42..210d7ccf638dd8 100644 --- a/lib/modules/manager/kustomize/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/kustomize/__snapshots__/extract.spec.ts.snap @@ -174,6 +174,15 @@ exports[`modules/manager/kustomize/extract extractPackageFile() parses helmChart "https://itzg.github.io/minecraft-server-charts", ], }, + { + "currentDigest": undefined, + "currentValue": "18.12.1", + "datasource": "docker", + "depName": "redis", + "depType": "HelmChart", + "packageName": "registry-1.docker.io/bitnamicharts/redis", + "pinDigests": false, + }, ], } `; diff --git a/lib/modules/manager/kustomize/extract.spec.ts b/lib/modules/manager/kustomize/extract.spec.ts index 75c2cb52cd9408..f6f2f67ac613b4 100644 --- a/lib/modules/manager/kustomize/extract.spec.ts +++ b/lib/modules/manager/kustomize/extract.spec.ts @@ -175,6 +175,22 @@ describe('modules/manager/kustomize/extract', () => { }); expect(pkg).toEqual(sample); }); + + it('should correctly extract an OCI chart', () => { + const sample = { + depName: 'redis', + packageName: 'registry-1.docker.io/bitnamicharts/redis', + currentValue: '18.12.1', + datasource: DockerDatasource.id, + pinDigests: false, + }; + const pkg = extractHelmChart({ + name: sample.depName, + version: sample.currentValue, + repo: 'oci://registry-1.docker.io/bitnamicharts', + }); + expect(pkg).toEqual(sample); + }); }); describe('image extraction', () => { @@ -279,33 +295,67 @@ describe('modules/manager/kustomize/extract', () => { }); expect(pkg).toEqual(sample); }); + + it('should correctly extract with registryAliases', () => { + const sample = { + autoReplaceStringTemplate: + '{{newValue}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentDigest: undefined, + currentValue: 'v1.0.0', + replaceString: 'v1.0.0', + datasource: DockerDatasource.id, + depName: 'docker.io/image/service', + }; + const pkg = extractImage( + { + name: 'localhost:5000/repo/image/service', + newTag: sample.currentValue, + }, + { 'localhost:5000/repo': 'docker.io' }, + ); + expect(pkg).toEqual(sample); + }); }); describe('extractPackageFile()', () => { it('returns null for non kustomize kubernetes files', () => { - expect(extractPackageFile(nonKustomize)).toBeNull(); + expect( + extractPackageFile(nonKustomize, 'kustomization.yaml', {}), + ).toBeNull(); }); it('extracts multiple image lines', () => { - const res = extractPackageFile(kustomizeWithLocal); + const res = extractPackageFile( + kustomizeWithLocal, + 'kustomization.yaml', + {}, + ); expect(res?.deps).toMatchSnapshot(); expect(res?.deps).toHaveLength(2); }); it('extracts ssh dependency', () => { - const res = extractPackageFile(kustomizeGitSSHBase); + const res = extractPackageFile( + kustomizeGitSSHBase, + 'kustomization.yaml', + {}, + ); expect(res?.deps).toMatchSnapshot(); expect(res?.deps).toHaveLength(1); }); it('extracts ssh dependency with a subdir', () => { - const res = extractPackageFile(kustomizeGitSSHSubdir); + const res = extractPackageFile( + kustomizeGitSSHSubdir, + 'kustomization.yaml', + {}, + ); expect(res?.deps).toMatchSnapshot(); expect(res?.deps).toHaveLength(1); }); it('extracts http dependency', () => { - const res = extractPackageFile(kustomizeHTTP); + const res = extractPackageFile(kustomizeHTTP, 'kustomization.yaml', {}); expect(res?.deps).toMatchSnapshot(); expect(res?.deps).toHaveLength(2); expect(res?.deps[0].currentValue).toBe('v0.0.1'); @@ -314,7 +364,7 @@ describe('modules/manager/kustomize/extract', () => { }); it('should extract out image versions', () => { - const res = extractPackageFile(gitImages); + const res = extractPackageFile(gitImages, 'kustomization.yaml', {}); expect(res?.deps).toMatchSnapshot(); expect(res?.deps).toHaveLength(6); expect(res?.deps[0].currentValue).toBe('v0.1.0'); @@ -323,15 +373,21 @@ describe('modules/manager/kustomize/extract', () => { }); it('ignores non-Kubernetes empty files', () => { - expect(extractPackageFile('')).toBeNull(); + expect(extractPackageFile('', 'kustomization.yaml', {})).toBeNull(); }); it('does nothing with kustomize empty kustomize files', () => { - expect(extractPackageFile(kustomizeEmpty)).toBeNull(); + expect( + extractPackageFile(kustomizeEmpty, 'kustomization.yaml', {}), + ).toBeNull(); }); it('should extract bases resources and components from their respective blocks', () => { - const res = extractPackageFile(kustomizeDepsInResources); + const res = extractPackageFile( + kustomizeDepsInResources, + 'kustomization.yaml', + {}, + ); expect(res).not.toBeNull(); expect(res?.deps).toMatchSnapshot(); expect(res?.deps).toHaveLength(3); @@ -347,7 +403,11 @@ describe('modules/manager/kustomize/extract', () => { }); it('should extract dependencies when kind is Component', () => { - const res = extractPackageFile(kustomizeComponent); + const res = extractPackageFile( + kustomizeComponent, + 'kustomization.yaml', + {}, + ); expect(res).not.toBeNull(); expect(res?.deps).toMatchSnapshot(); expect(res?.deps).toHaveLength(3); @@ -366,7 +426,9 @@ describe('modules/manager/kustomize/extract', () => { 'sha256:b0cfe264cb1143c7c660ddfd5c482464997d62d6bc9f97f8fdf3deefce881a8c'; it('extracts from newTag', () => { - expect(extractPackageFile(newTag)).toMatchSnapshot({ + expect( + extractPackageFile(newTag, 'kustomization.yaml', {}), + ).toMatchSnapshot({ deps: [ { currentDigest: undefined, @@ -386,7 +448,9 @@ describe('modules/manager/kustomize/extract', () => { }); it('extracts from digest', () => { - expect(extractPackageFile(digest)).toMatchSnapshot({ + expect( + extractPackageFile(digest, 'kustomization.yaml', {}), + ).toMatchSnapshot({ deps: [ { currentDigest: postgresDigest, @@ -412,7 +476,9 @@ describe('modules/manager/kustomize/extract', () => { }); it('extracts newName', () => { - expect(extractPackageFile(newName)).toMatchSnapshot({ + expect( + extractPackageFile(newName, 'kustomization.yaml', {}), + ).toMatchSnapshot({ deps: [ { depName: 'awesome/postgres', @@ -440,15 +506,28 @@ describe('modules/manager/kustomize/extract', () => { }); it('parses helmChart field', () => { - const res = extractPackageFile(kustomizeHelmChart); + const res = extractPackageFile( + kustomizeHelmChart, + 'kustomization.yaml', + {}, + ); expect(res).toMatchSnapshot({ deps: [ { depType: 'HelmChart', depName: 'minecraft', currentValue: '3.1.3', + datasource: HelmDatasource.id, registryUrls: ['https://itzg.github.io/minecraft-server-charts'], }, + { + depType: 'HelmChart', + depName: 'redis', + currentValue: '18.12.1', + datasource: DockerDatasource.id, + packageName: 'registry-1.docker.io/bitnamicharts/redis', + pinDigests: false, + }, ], }); }); diff --git a/lib/modules/manager/kustomize/extract.ts b/lib/modules/manager/kustomize/extract.ts index 3c021d6fad7bd9..2e5b3d80d832aa 100644 --- a/lib/modules/manager/kustomize/extract.ts +++ b/lib/modules/manager/kustomize/extract.ts @@ -3,12 +3,16 @@ import { logger } from '../../../logger'; import { coerceArray } from '../../../util/array'; import { regEx } from '../../../util/regex'; import { parseSingleYaml } from '../../../util/yaml'; -import { DockerDatasource } from '../../datasource/docker'; import { GitTagsDatasource } from '../../datasource/git-tags'; import { GithubTagsDatasource } from '../../datasource/github-tags'; import { HelmDatasource } from '../../datasource/helm'; -import { splitImageParts } from '../dockerfile/extract'; -import type { PackageDependency, PackageFileContent } from '../types'; +import { getDep } from '../dockerfile/extract'; +import { isOCIRegistry, removeOCIPrefix } from '../helmv3/oci'; +import type { + ExtractConfig, + PackageDependency, + PackageFileContent, +} from '../types'; import type { HelmChart, Image, Kustomize } from './types'; // URL specifications should follow the hashicorp URL format @@ -63,7 +67,10 @@ export function extractResource(base: string): PackageDependency | null { }; } -export function extractImage(image: Image): PackageDependency | null { +export function extractImage( + image: Image, + aliases?: Record | undefined, +): PackageDependency | null { if (!image.name) { return null; } @@ -72,7 +79,7 @@ export function extractImage(image: Image): PackageDependency | null { logger.debug({ image }, 'Invalid image name'); return null; } - const nameDep = splitImageParts(nameToSplit); + const nameDep = getDep(nameToSplit, false, aliases); const { depName } = nameDep; const { digest, newTag } = image; if (digest && newTag) { @@ -98,9 +105,7 @@ export function extractImage(image: Image): PackageDependency | null { } return { - datasource: DockerDatasource.id, - depName, - currentValue: nameDep.currentValue, + ...nameDep, currentDigest: digest, replaceString: digest, }; @@ -115,11 +120,9 @@ export function extractImage(image: Image): PackageDependency | null { }; } - // TODO: types (#22198) - const dep = splitImageParts(`${depName}:${newTag}`); + const dep = getDep(`${depName}:${newTag}`, false, aliases); return { ...dep, - datasource: DockerDatasource.id, replaceString: newTag, autoReplaceStringTemplate: '{{newValue}}{{#if newDigest}}@{{newDigest}}{{/if}}', @@ -129,7 +132,6 @@ export function extractImage(image: Image): PackageDependency | null { if (image.newName) { return { ...nameDep, - datasource: DockerDatasource.id, replaceString: image.newName, }; } @@ -139,11 +141,28 @@ export function extractImage(image: Image): PackageDependency | null { export function extractHelmChart( helmChart: HelmChart, + aliases?: Record | undefined, ): PackageDependency | null { if (!helmChart.name) { return null; } + if (isOCIRegistry(helmChart.repo)) { + const dep = getDep( + `${removeOCIPrefix(helmChart.repo)}/${helmChart.name}:${helmChart.version}`, + false, + aliases, + ); + return { + ...dep, + depName: helmChart.name, + packageName: dep.depName, + // https://github.com/helm/helm/issues/10312 + // https://github.com/helm/helm/issues/10678 + pinDigests: false, + }; + } + return { depName: helmChart.name, currentValue: helmChart.version, @@ -180,9 +199,10 @@ export function parseKustomize( export function extractPackageFile( content: string, - packageFile?: string, // TODO: fix tests + packageFile: string, + config: ExtractConfig, ): PackageFileContent | null { - logger.trace(`kustomize.extractPackageFile(${packageFile!})`); + logger.trace(`kustomize.extractPackageFile(${packageFile})`); const deps: PackageDependency[] = []; const pkg = parseKustomize(content, packageFile); @@ -225,7 +245,7 @@ export function extractPackageFile( // grab the image tags for (const image of coerceArray(pkg.images)) { - const dep = extractImage(image); + const dep = extractImage(image, config.registryAliases); if (dep) { deps.push({ ...dep, @@ -236,7 +256,7 @@ export function extractPackageFile( // grab the helm charts for (const helmChart of coerceArray(pkg.helmCharts)) { - const dep = extractHelmChart(helmChart); + const dep = extractHelmChart(helmChart, config.registryAliases); if (dep) { deps.push({ ...dep, diff --git a/lib/modules/manager/kustomize/readme.md b/lib/modules/manager/kustomize/readme.md index ed665229dc393d..a5593b4a7f3a19 100644 --- a/lib/modules/manager/kustomize/readme.md +++ b/lib/modules/manager/kustomize/readme.md @@ -18,6 +18,7 @@ This manager uses three `depType`s to allow fine-grained control of which depend - Component - Kustomization - HelmChart +- OCIChart **Limitations** diff --git a/lib/modules/manager/maven/__fixtures__/minimum_snapshot.pom.xml b/lib/modules/manager/maven/__fixtures__/minimum_snapshot.pom.xml new file mode 100644 index 00000000000000..37ffe02f8e3523 --- /dev/null +++ b/lib/modules/manager/maven/__fixtures__/minimum_snapshot.pom.xml @@ -0,0 +1,6 @@ + + 4.0.0 + com.mycompany.app + my-app + 0.0.1-SNAPSHOT + diff --git a/lib/modules/manager/maven/extract.spec.ts b/lib/modules/manager/maven/extract.spec.ts index 4c8fc2ab43077e..d6689de4238d70 100644 --- a/lib/modules/manager/maven/extract.spec.ts +++ b/lib/modules/manager/maven/extract.spec.ts @@ -3,6 +3,7 @@ import { Fixtures } from '../../../../test/fixtures'; import { fs } from '../../../../test/util'; import { extractAllPackageFiles, + extractExtensions, extractPackage, extractRegistries, resolveParents, @@ -243,6 +244,20 @@ describe('modules/manager/maven/extract', () => { packageFileVersion: '1', }); }); + + it('tries minimum snapshot manifests', () => { + const res = extractPackage( + Fixtures.get(`minimum_snapshot.pom.xml`), + 'some-file', + ); + expect(res).toEqual({ + datasource: 'maven', + deps: [], + mavenProps: {}, + packageFile: 'some-file', + packageFileVersion: '0.0.1-SNAPSHOT', + }); + }); }); describe('resolveParents', () => { @@ -351,6 +366,27 @@ describe('modules/manager/maven/extract', () => { }); }); + describe('extractExtensions', () => { + it('returns null for invalid xml files', () => { + expect(extractExtensions('', '.mvn/extensions.xml')).toBeNull(); + expect( + extractExtensions('invalid xml content', '.mvn/extensions.xml'), + ).toBeNull(); + expect( + extractExtensions('', '.mvn/extensions.xml'), + ).toBeNull(); + expect( + extractExtensions('', '.mvn/extensions.xml'), + ).toBeNull(); + expect( + extractExtensions( + '', + '.mvn/extensions.xml', + ), + ).toBeNull(); + }); + }); + describe('extractAllPackageFiles', () => { it('should return empty if package has no content', async () => { fs.readLocalFile.mockResolvedValueOnce(''); @@ -692,6 +728,47 @@ describe('modules/manager/maven/extract', () => { ]); }); + it('should extract from .mvn/extensions.xml file', async () => { + fs.readLocalFile.mockResolvedValueOnce(codeBlock` + + + io.jenkins.tools.incrementals + git-changelist-maven-extension + 1.6 + + + `); + const res = await extractAllPackageFiles({}, ['.mvn/extensions.xml']); + expect(res).toMatchObject([ + { + packageFile: '.mvn/extensions.xml', + deps: [ + { + datasource: 'maven', + depName: + 'io.jenkins.tools.incrementals:git-changelist-maven-extension', + currentValue: '1.6', + depType: 'build', + fileReplacePosition: 372, + registryUrls: ['https://repo.maven.apache.org/maven2'], + }, + ], + }, + ]); + }); + + it('should return empty array if extensions file is invalid or empty', async () => { + fs.readLocalFile + .mockResolvedValueOnce('') + .mockResolvedValueOnce('invalid xml content'); + expect( + await extractAllPackageFiles({}, [ + '.mvn/extensions.xml', + 'grp/.mvn/extensions.xml', + ]), + ).toBeEmptyArray(); + }); + describe('root pom handling', () => { it('should skip root pom.xml', async () => { fs.readLocalFile.mockResolvedValueOnce(codeBlock` diff --git a/lib/modules/manager/maven/extract.ts b/lib/modules/manager/maven/extract.ts index 061c28fd92160d..c19b2de1c42df8 100644 --- a/lib/modules/manager/maven/extract.ts +++ b/lib/modules/manager/maven/extract.ts @@ -15,6 +15,12 @@ const supportedNamespaces = [ 'http://maven.apache.org/SETTINGS/1.2.0', ]; +const supportedExtensionsNamespaces = [ + 'http://maven.apache.org/EXTENSIONS/1.0.0', + 'http://maven.apache.org/EXTENSIONS/1.1.0', + 'http://maven.apache.org/EXTENSIONS/1.2.0', +]; + function parsePom(raw: string, packageFile: string): XmlDocument | null { let project: XmlDocument; try { @@ -39,6 +45,27 @@ function parsePom(raw: string, packageFile: string): XmlDocument | null { return null; } +function parseExtensions(raw: string, packageFile: string): XmlDocument | null { + let extensions: XmlDocument; + try { + extensions = new XmlDocument(raw); + } catch (err) { + logger.debug({ packageFile }, `Failed to parse as XML`); + return null; + } + const { name, attr, children } = extensions; + if (name !== 'extensions') { + return null; + } + if (!supportedExtensionsNamespaces.includes(attr.xmlns)) { + return null; + } + if (!is.nonEmptyArray(children)) { + return null; + } + return extensions; +} + function containsPlaceholder(str: string | null | undefined): boolean { return !!str && regEx(/\${[^}]*?}/).test(str); } @@ -476,6 +503,30 @@ function cleanResult(packageFiles: MavenInterimPackageFile[]): PackageFile[] { return packageFiles; } +export function extractExtensions( + rawContent: string, + packageFile: string, +): PackageFile | null { + if (!rawContent) { + return null; + } + + const extensions = parseExtensions(rawContent, packageFile); + if (!extensions) { + return null; + } + + const result: MavenInterimPackageFile = { + datasource: MavenDatasource.id, + packageFile, + deps: [], + }; + + result.deps = deepExtract(extensions); + + return result; +} + export async function extractAllPackageFiles( _config: ExtractConfig, packageFiles: string[], @@ -498,6 +549,13 @@ export async function extractAllPackageFiles( ); additionalRegistryUrls.push(...registries); } + } else if (packageFile.endsWith('.mvn/extensions.xml')) { + const extensions = extractExtensions(content, packageFile); + if (extensions) { + packages.push(extensions); + } else { + logger.trace({ packageFile }, 'can not read extensions'); + } } else { const pkg = extractPackage(content, packageFile); if (pkg) { diff --git a/lib/modules/manager/maven/index.ts b/lib/modules/manager/maven/index.ts index 8d12ca432bb606..bd282a23395db6 100644 --- a/lib/modules/manager/maven/index.ts +++ b/lib/modules/manager/maven/index.ts @@ -6,7 +6,11 @@ export { extractAllPackageFiles } from './extract'; export { bumpPackageVersion, updateDependency } from './update'; export const defaultConfig = { - fileMatch: ['(^|/|\\.)pom\\.xml$', '^(((\\.mvn)|(\\.m2))/)?settings\\.xml$'], + fileMatch: [ + '(^|/|\\.)pom\\.xml$', + '^(((\\.mvn)|(\\.m2))/)?settings\\.xml$', + '(^|/)\\.mvn/extensions\\.xml$', + ], versioning: mavenVersioning.id, }; diff --git a/lib/modules/manager/maven/update.spec.ts b/lib/modules/manager/maven/update.spec.ts index 5c8c6ab52b360d..a6f9a5717a361e 100644 --- a/lib/modules/manager/maven/update.spec.ts +++ b/lib/modules/manager/maven/update.spec.ts @@ -5,6 +5,7 @@ import { bumpPackageVersion, updateDependency } from './update'; const simpleContent = Fixtures.get(`simple.pom.xml`); const minimumContent = Fixtures.get(`minimum.pom.xml`); +const minimumSnapshotContent = Fixtures.get(`minimum_snapshot.pom.xml`); const prereleaseContent = Fixtures.get(`prerelease.pom.xml`); describe('modules/manager/maven/update', () => { @@ -30,6 +31,53 @@ describe('modules/manager/maven/update', () => { expect(project.valueWithPath('version')).toBe('0.0.2'); }); + it('bumps pom.xml version keeping SNAPSHOT', () => { + const { bumpedContent } = bumpPackageVersion( + minimumSnapshotContent, + '0.0.1-SNAPSHOT', + 'patch', + ); + + const project = new XmlDocument(bumpedContent!); + expect(project.valueWithPath('version')).toBe('0.0.2-SNAPSHOT'); + }); + + it('bumps pom.xml minor version keeping SNAPSHOT', () => { + const { bumpedContent } = bumpPackageVersion( + minimumSnapshotContent, + '0.0.1-SNAPSHOT', + 'minor', + ); + + const project = new XmlDocument(bumpedContent!); + expect(project.valueWithPath('version')).toBe('0.1.0-SNAPSHOT'); + }); + + it('bumps pom.xml major version keeping SNAPSHOT', () => { + const { bumpedContent } = bumpPackageVersion( + minimumSnapshotContent, + '0.0.1-SNAPSHOT', + 'major', + ); + + const project = new XmlDocument(bumpedContent!); + expect(project.valueWithPath('version')).toBe('1.0.0-SNAPSHOT'); + }); + + it('bumps pom.xml version keeping qualifier with -SNAPSHOT', () => { + const { bumpedContent } = bumpPackageVersion( + minimumSnapshotContent.replace( + '0.0.1-SNAPSHOT', + '0.0.1-qualified-SNAPSHOT', + ), + '0.0.1-qualified-SNAPSHOT', + 'patch', + ); + + const project = new XmlDocument(bumpedContent!); + expect(project.valueWithPath('version')).toBe('0.0.2-qualified-SNAPSHOT'); + }); + it('does not bump version twice', () => { const { bumpedContent } = bumpPackageVersion( simpleContent, @@ -71,6 +119,17 @@ describe('modules/manager/maven/update', () => { expect(bumpedContent).toEqual(simpleContent); }); + it('bumps pom.xml version to SNAPSHOT with prerelease', () => { + const { bumpedContent } = bumpPackageVersion( + simpleContent, + '0.0.1', + 'prerelease', + ); + + const project = new XmlDocument(bumpedContent!); + expect(project.valueWithPath('version')).toBe('0.0.2-SNAPSHOT'); + }); + it('bumps pom.xml version with prerelease semver level', () => { const { bumpedContent } = bumpPackageVersion( prereleaseContent, diff --git a/lib/modules/manager/maven/update.ts b/lib/modules/manager/maven/update.ts index 055d033cccb9ac..1d89f30f5c3ed7 100644 --- a/lib/modules/manager/maven/update.ts +++ b/lib/modules/manager/maven/update.ts @@ -1,3 +1,4 @@ +import is from '@sindresorhus/is'; import semver, { ReleaseType } from 'semver'; import { XmlDocument } from 'xmldoc'; import { logger } from '../../../logger'; @@ -78,7 +79,31 @@ export function bumpPackageVersion( const startTagPosition = versionNode.startTagPosition; const versionPosition = content.indexOf(versionNode.val, startTagPosition); - const newPomVersion = semver.inc(currentValue, bumpVersion); + let newPomVersion: string | null = null; + const currentPrereleaseValue = semver.prerelease(currentValue); + if (isSnapshot(currentPrereleaseValue)) { + // It is already a SNAPSHOT version. + // Therefore the same qualifier (prerelease) will be used as before. + let releaseType = bumpVersion; + if (!bumpVersion.startsWith('pre')) { + releaseType = `pre${bumpVersion}` as ReleaseType; + } + newPomVersion = semver.inc( + currentValue, + releaseType, + currentPrereleaseValue!.join('.'), + false, + ); + } else if (currentPrereleaseValue) { + // Some qualifier which is not a SNAPSHOT is present. + // The expected behaviour in this case is unclear and the standard increase will be used. + newPomVersion = semver.inc(currentValue, bumpVersion); + } else { + // A release version without any qualifier is present. + // Therefore the SNAPSHOT qualifier will be added if a prerelease is requested. + // This will do a normal increment, ignoring SNAPSHOT, if a non-prerelease bumpVersion is configured + newPomVersion = semver.inc(currentValue, bumpVersion, 'SNAPSHOT', false); + } if (!newPomVersion) { throw new Error('semver inc failed'); } @@ -108,3 +133,10 @@ export function bumpPackageVersion( } return { bumpedContent }; } + +function isSnapshot( + prerelease: ReadonlyArray | null, +): boolean { + const lastPart = prerelease?.at(-1); + return is.string(lastPart) && lastPart.endsWith('SNAPSHOT'); +} diff --git a/lib/modules/manager/npm/extract/common/package-file.ts b/lib/modules/manager/npm/extract/common/package-file.ts index 31048ee922cdce..03882c1b172bdb 100644 --- a/lib/modules/manager/npm/extract/common/package-file.ts +++ b/lib/modules/manager/npm/extract/common/package-file.ts @@ -47,6 +47,7 @@ export function extractPackageJson( resolutions: 'resolutions', packageManager: 'packageManager', overrides: 'overrides', + pnpm: 'pnpm', }; for (const depType of Object.keys(depTypes) as (keyof typeof depTypes)[]) { @@ -82,6 +83,28 @@ export function extractPackageJson( val as unknown as NpmManagerData, ), ); + } else if (depType === 'pnpm' && depName === 'overrides') { + for (const [overridesKey, overridesVal] of Object.entries( + val as unknown as NpmPackageDependency, + )) { + if (is.string(overridesVal)) { + dep = { + depName: overridesKey, + depType: 'pnpm.overrides', + ...extractDependency(depName, overridesKey, overridesVal), + }; + setNodeCommitTopic(dep); + dep.prettyDepType = depTypes[depName]; + deps.push(dep); + } else if (is.object(overridesVal)) { + deps.push( + ...extractOverrideDepsRec( + [overridesKey], + overridesVal as unknown as NpmManagerData, + ), + ); + } + } } else { // TODO: fix type #22198 dep = { ...dep, ...extractDependency(depType, depName, val!) }; diff --git a/lib/modules/manager/npm/extract/index.spec.ts b/lib/modules/manager/npm/extract/index.spec.ts index 428ad3c13e8b7a..07bf5524a7cae6 100644 --- a/lib/modules/manager/npm/extract/index.spec.ts +++ b/lib/modules/manager/npm/extract/index.spec.ts @@ -1,3 +1,5 @@ +import { codeBlock } from 'common-tags'; +import { extractAllPackageFiles } from '..'; import { Fixtures } from '../../../../../test/fixtures'; import { fs } from '../../../../../test/util'; import { logger } from '../../../../logger'; @@ -293,6 +295,30 @@ describe('modules/manager/npm/extract/index', () => { ).toBeArrayIncludingOnly(['https://registry.example.com']); }); + it('resolves registry URLs using the package name if set', async () => { + fs.readLocalFile.mockImplementation((fileName): Promise => { + if (fileName === '.yarnrc.yml') { + return Promise.resolve(codeBlock` + npmScopes: + yarnpkg: + npmRegistryServer: https://registry.example.com + `); + } + return Promise.resolve(null); + }); + const res = await npmExtract.extractPackageFile( + '{"packageManager": "yarn@4.1.1"}', + 'package.json', + {}, + ); + expect(res?.deps).toEqual([ + expect.objectContaining({ + depName: 'yarn', + registryUrls: ['https://registry.example.com'], + }), + ]); + }); + it('finds complex yarn workspaces', async () => { fs.readLocalFile.mockImplementation((fileName): Promise => { return Promise.resolve(null); @@ -869,15 +895,95 @@ describe('modules/manager/npm/extract/index', () => { ], }); }); + + it('extracts dependencies from pnpm.overrides', async () => { + const content = `{ + "devDependencies": { + "@types/react": "18.0.5" + }, + "pnpm": { + "overrides": { + "node": "8.9.2", + "@types/react": "18.0.5", + "baz": { + "node": "8.9.2", + "bar": { + "foo": "1.0.0" + } + }, + "foo2": { + ".": "1.0.0", + "bar2": "1.0.0" + }, + "emptyObject":{} + } + } + }`; + const res = await npmExtract.extractPackageFile( + content, + 'package.json', + defaultExtractConfig, + ); + expect(res).toMatchObject({ + deps: [ + { + depType: 'devDependencies', + depName: '@types/react', + currentValue: '18.0.5', + datasource: 'npm', + prettyDepType: 'devDependency', + }, + { + depType: 'pnpm.overrides', + depName: 'node', + currentValue: '8.9.2', + datasource: 'npm', + commitMessageTopic: 'Node.js', + prettyDepType: 'overrides', + }, + { + depType: 'pnpm.overrides', + depName: '@types/react', + currentValue: '18.0.5', + datasource: 'npm', + prettyDepType: 'overrides', + }, + { + depName: 'node', + managerData: { parents: ['baz'] }, + commitMessageTopic: 'Node.js', + currentValue: '8.9.2', + datasource: 'npm', + }, + { + depName: 'foo', + managerData: { parents: ['baz', 'bar'] }, + currentValue: '1.0.0', + datasource: 'npm', + }, + { + depName: 'foo2', + managerData: { parents: ['foo2'] }, + currentValue: '1.0.0', + datasource: 'npm', + }, + { + depName: 'bar2', + managerData: { parents: ['foo2'] }, + currentValue: '1.0.0', + datasource: 'npm', + }, + ], + }); + }); }); describe('.extractAllPackageFiles()', () => { it('runs', async () => { fs.readLocalFile.mockResolvedValueOnce(input02Content); - const res = await npmExtract.extractAllPackageFiles( - defaultExtractConfig, - ['package.json'], - ); + const res = await extractAllPackageFiles(defaultExtractConfig, [ + 'package.json', + ]); expect(res).toEqual([ { deps: [ diff --git a/lib/modules/manager/npm/extract/index.ts b/lib/modules/manager/npm/extract/index.ts index 1fc7c097b19c76..a6606e1fb7f7e3 100644 --- a/lib/modules/manager/npm/extract/index.ts +++ b/lib/modules/manager/npm/extract/index.ts @@ -177,7 +177,7 @@ export async function extractPackageFile( for (const dep of res.deps) { if (dep.depName) { const registryUrlFromYarnConfig = resolveRegistryUrl( - dep.depName, + dep.packageName ?? dep.depName, yarnConfig, ); if (registryUrlFromYarnConfig && dep.datasource === NpmDatasource.id) { diff --git a/lib/modules/manager/npm/extract/types.ts b/lib/modules/manager/npm/extract/types.ts index fbfbb6546960fe..a9681aec758eb1 100644 --- a/lib/modules/manager/npm/extract/types.ts +++ b/lib/modules/manager/npm/extract/types.ts @@ -14,6 +14,9 @@ export type NpmPackage = PackageJson & { dependenciesMeta?: DependenciesMeta; overrides?: OverrideDependency; volta?: PackageJson.Dependency; + pnpm?: { + overrides?: PackageJson.Dependency; + }; }; export type LockFileEntry = Record< diff --git a/lib/modules/manager/npm/extract/yarn.spec.ts b/lib/modules/manager/npm/extract/yarn.spec.ts index 548efc97d07a36..824059547445ed 100644 --- a/lib/modules/manager/npm/extract/yarn.spec.ts +++ b/lib/modules/manager/npm/extract/yarn.spec.ts @@ -58,6 +58,12 @@ describe('modules/manager/npm/extract/yarn', () => { it('getYarnVersionFromLock', () => { expect(getYarnVersionFromLock({ isYarn1: true })).toBe('^1.22.18'); + expect( + getYarnVersionFromLock({ isYarn1: false, lockfileVersion: 12 }), + ).toBe('>=4.0.0'); + expect( + getYarnVersionFromLock({ isYarn1: false, lockfileVersion: 10 }), + ).toBe('^4.0.0'); expect(getYarnVersionFromLock({ isYarn1: false, lockfileVersion: 8 })).toBe( '^3.0.0', ); diff --git a/lib/modules/manager/npm/extract/yarn.ts b/lib/modules/manager/npm/extract/yarn.ts index cee4f157762780..77a7cf9afb964d 100644 --- a/lib/modules/manager/npm/extract/yarn.ts +++ b/lib/modules/manager/npm/extract/yarn.ts @@ -104,6 +104,13 @@ export function getYarnVersionFromLock(lockfile: LockFile): string { if (isYarn1) { return '^1.22.18'; } + if (lockfileVersion && lockfileVersion >= 12) { + // This will probably be v5 + return '>=4.0.0'; + } + if (lockfileVersion && lockfileVersion >= 10) { + return '^4.0.0'; + } if (lockfileVersion && lockfileVersion >= 8) { // https://github.com/yarnpkg/berry/commit/9bcd27ae34aee77a567dd104947407532fa179b3 return '^3.0.0'; diff --git a/lib/modules/manager/npm/post-update/index.spec.ts b/lib/modules/manager/npm/post-update/index.spec.ts index 1acaa10f789899..600afddf124aef 100644 --- a/lib/modules/manager/npm/post-update/index.spec.ts +++ b/lib/modules/manager/npm/post-update/index.spec.ts @@ -204,23 +204,6 @@ describe('modules/manager/npm/post-update/index', () => { expect(git.getFile).toHaveBeenCalledOnce(); }); - it('works no reuse lockfiles', async () => { - await expect( - writeExistingFiles( - { ...updateConfig, reuseLockFiles: false }, - additionalFiles, - ), - ).resolves.toBeUndefined(); - - expect(fs.writeLocalFile).toHaveBeenCalledOnce(); - expect(fs.deleteLocalFile.mock.calls).toEqual([ - ['package-lock.json'], - ['yarn.lock'], - ['yarn.lock'], - ['packages/pnpm/pnpm-lock.yaml'], - ]); - }); - it('writes .npmrc files', async () => { await writeExistingFiles(updateConfig, { npm: [ diff --git a/lib/modules/manager/npm/post-update/index.ts b/lib/modules/manager/npm/post-update/index.ts index 3ee90a8c9c5b44..76265c2218f7eb 100644 --- a/lib/modules/manager/npm/post-update/index.ts +++ b/lib/modules/manager/npm/post-update/index.ts @@ -133,7 +133,7 @@ export async function writeExistingFiles( const npmrcFilename = upath.join(basedir, '.npmrc'); if (is.string(npmrc)) { try { - await writeLocalFile(npmrcFilename, `${npmrc}\n`); + await writeLocalFile(npmrcFilename, npmrc.replace(/\n?$/, '\n')); } catch (err) /* istanbul ignore next */ { logger.warn({ npmrcFilename, err }, 'Error writing .npmrc'); } @@ -141,98 +141,76 @@ export async function writeExistingFiles( const npmLock = packageFile.managerData.npmLock; if (npmLock) { const npmLockPath = npmLock; - if ( - process.env.RENOVATE_REUSE_PACKAGE_LOCK === 'false' || - config.reuseLockFiles === false - ) { - logger.debug(`Ensuring ${npmLock} is removed`); - await deleteLocalFile(npmLockPath); - } else { - logger.debug(`Writing ${npmLock}`); - let existingNpmLock: string; - try { - existingNpmLock = (await getFile(npmLock)) ?? ''; - } catch (err) /* istanbul ignore next */ { - logger.warn({ err }, 'Error reading npm lock file'); - existingNpmLock = ''; - } - const { detectedIndent, lockFileParsed: npmLockParsed } = - parseLockFile(existingNpmLock); - if (npmLockParsed) { - const packageNames = - 'packages' in npmLockParsed - ? Object.keys(npmLockParsed.packages) - : []; - const widens: string[] = []; - let lockFileChanged = false; - for (const upgrade of config.upgrades) { - if (upgrade.lockFiles && !upgrade.lockFiles.includes(npmLock)) { - continue; - } - if (!upgrade.managerData) { - continue; - } + logger.debug(`Writing ${npmLock}`); + let existingNpmLock: string; + try { + existingNpmLock = (await getFile(npmLock)) ?? ''; + } catch (err) /* istanbul ignore next */ { + logger.warn({ err }, 'Error reading npm lock file'); + existingNpmLock = ''; + } + const { detectedIndent, lockFileParsed: npmLockParsed } = + parseLockFile(existingNpmLock); + if (npmLockParsed) { + const packageNames = + 'packages' in npmLockParsed + ? Object.keys(npmLockParsed.packages) + : []; + const widens: string[] = []; + let lockFileChanged = false; + for (const upgrade of config.upgrades) { + if (upgrade.lockFiles && !upgrade.lockFiles.includes(npmLock)) { + continue; + } + if (!upgrade.managerData) { + continue; + } + if ( + upgrade.rangeStrategy === 'widen' && + upgrade.managerData.npmLock === npmLock + ) { + // TODO #22198 + widens.push(upgrade.depName!); + } + const { depName } = upgrade; + for (const packageName of packageNames) { if ( - upgrade.rangeStrategy === 'widen' && - upgrade.managerData.npmLock === npmLock + 'packages' in npmLockParsed && + (packageName === `node_modules/${depName}` || + packageName.startsWith(`node_modules/${depName}/`)) ) { - // TODO #22198 - widens.push(upgrade.depName!); - } - const { depName } = upgrade; - for (const packageName of packageNames) { - if ( - 'packages' in npmLockParsed && - (packageName === `node_modules/${depName}` || - packageName.startsWith(`node_modules/${depName}/`)) - ) { - logger.trace({ packageName }, 'Massaging out package name'); - lockFileChanged = true; - delete npmLockParsed.packages[packageName]; - } + logger.trace({ packageName }, 'Massaging out package name'); + lockFileChanged = true; + delete npmLockParsed.packages[packageName]; } } - if (widens.length) { - logger.debug( - `Removing ${String(widens)} from ${npmLock} to force an update`, - ); - lockFileChanged = true; - try { - if ( - 'dependencies' in npmLockParsed && - npmLockParsed.dependencies - ) { - widens.forEach((depName) => { - // TODO #22198 - delete npmLockParsed.dependencies![depName]; - }); - } - } catch (err) /* istanbul ignore next */ { - logger.warn( - { npmLock }, - 'Error massaging package-lock.json for widen', - ); + } + if (widens.length) { + logger.debug( + `Removing ${String(widens)} from ${npmLock} to force an update`, + ); + lockFileChanged = true; + try { + if ('dependencies' in npmLockParsed && npmLockParsed.dependencies) { + widens.forEach((depName) => { + // TODO #22198 + delete npmLockParsed.dependencies![depName]; + }); } + } catch (err) /* istanbul ignore next */ { + logger.warn( + { npmLock }, + 'Error massaging package-lock.json for widen', + ); } - if (lockFileChanged) { - logger.debug('Massaging npm lock file before writing to disk'); - existingNpmLock = composeLockFile(npmLockParsed, detectedIndent); - } - await writeLocalFile(npmLockPath, existingNpmLock); } + if (lockFileChanged) { + logger.debug('Massaging npm lock file before writing to disk'); + existingNpmLock = composeLockFile(npmLockParsed, detectedIndent); + } + await writeLocalFile(npmLockPath, existingNpmLock); } } - const { yarnLock } = packageFile.managerData; - if (yarnLock && config.reuseLockFiles === false) { - await deleteLocalFile(yarnLock); - } - // istanbul ignore next - if ( - packageFile.managerData.pnpmShrinkwrap && - config.reuseLockFiles === false - ) { - await deleteLocalFile(packageFile.managerData.pnpmShrinkwrap); - } } } diff --git a/lib/modules/manager/npm/post-update/npm.ts b/lib/modules/manager/npm/post-update/npm.ts index d89caf0dccec71..728945fdb7da60 100644 --- a/lib/modules/manager/npm/post-update/npm.ts +++ b/lib/modules/manager/npm/post-update/npm.ts @@ -191,7 +191,7 @@ export async function generateLockFile( } catch (err) /* istanbul ignore next */ { logger.debug( { err, lockFileName }, - 'Error removing package-lock.json for lock file maintenance', + 'Error removing `package-lock.json` for lock file maintenance', ); } } diff --git a/lib/modules/manager/npm/post-update/pnpm.spec.ts b/lib/modules/manager/npm/post-update/pnpm.spec.ts index 8ba7ba957a3b9c..6c9d28db1b8a50 100644 --- a/lib/modules/manager/npm/post-update/pnpm.spec.ts +++ b/lib/modules/manager/npm/post-update/pnpm.spec.ts @@ -360,7 +360,7 @@ describe('modules/manager/npm/post-update/pnpm', () => { expect(res).toBeNull(); }); - it('returns null if lockfileVersion is not a number', async () => { + it('returns null if lockfileVersion is not a number or numeric string', async () => { fs.readLocalFile.mockResolvedValueOnce('lockfileVersion: foo\n'); const res = await pnpmHelper.getConstraintFromLockFile('some-file-name'); expect(res).toBeNull(); @@ -377,5 +377,17 @@ describe('modules/manager/npm/post-update/pnpm', () => { const res = await pnpmHelper.getConstraintFromLockFile('some-file-name'); expect(res).toBe('>=6 <7'); }); + + it('maps supported versions for v6', async () => { + fs.readLocalFile.mockResolvedValueOnce("lockfileVersion: '6.0'\n"); + const res = await pnpmHelper.getConstraintFromLockFile('some-file-name'); + expect(res).toBe('>=7.24.2 <9'); + }); + + it('maps supported versions for v9', async () => { + fs.readLocalFile.mockResolvedValueOnce("lockfileVersion: '9.0'\n"); + const res = await pnpmHelper.getConstraintFromLockFile('some-file-name'); + expect(res).toBe('>=9'); + }); }); }); diff --git a/lib/modules/manager/npm/post-update/pnpm.ts b/lib/modules/manager/npm/post-update/pnpm.ts index 13348c851fa631..d5f9db4f280bfb 100644 --- a/lib/modules/manager/npm/post-update/pnpm.ts +++ b/lib/modules/manager/npm/post-update/pnpm.ts @@ -111,7 +111,7 @@ export async function generateLockFile( } catch (err) /* istanbul ignore next */ { logger.debug( { err, lockFileName }, - 'Error removing yarn.lock for lock file maintenance', + 'Error removing `pnpm-lock.yaml` for lock file maintenance', ); } } @@ -144,11 +144,16 @@ export async function getConstraintFromLockFile( try { const lockfileContent = await readLocalFile(lockFileName, 'utf8'); if (!lockfileContent) { + logger.trace(`Empty pnpm lock file: ${lockFileName}`); return null; } // TODO: use schema (#9610) const pnpmLock = parseSingleYaml(lockfileContent); - if (!is.number(pnpmLock?.lockfileVersion)) { + if ( + !is.number(pnpmLock?.lockfileVersion) && + !is.numericString(pnpmLock?.lockfileVersion) + ) { + logger.trace(`Invalid pnpm lockfile version: ${lockFileName}`); return null; } // find matching lockfileVersion and use its constraints @@ -172,25 +177,31 @@ export async function getConstraintFromLockFile( } /** - pnpm lockfiles have corresponding version numbers called "lockfileVersion" - each lockfileVersion can only be generated by a certain pnpm version ranges - eg. lockfileVersion: 5.4 can only be generated by pnpm version >=7 && <8 - official list can be found here : https://github.com/pnpm/spec/tree/master/lockfile - we use the mapping present below to find the compatible pnpm version range for a given lockfileVersion - - the various terms used in the mapping are explained below: - lowerConstriant : lowest pnpm version that can generate the lockfileVersion - upperConstraint : highest pnpm version that can generate the lockfileVersion - lowerBound : highest pnpm version that is less than the lowerConstraint - upperBound : lowest pnpm version that is greater than upperConstraint - - For handling future lockfileVersions, we need to: - 1. add a upperBound and upperConstraint to the current lastest lockfileVersion - 2. add an object for the new lockfileVersion with lowerBound and lowerConstraint + * pnpm lockfiles have corresponding version numbers called "lockfileVersion" + * each lockfileVersion can only be generated by a certain pnpm version range + * eg. lockfileVersion: 5.4 can only be generated by pnpm version >=7 && <8 + * official list can be found here : https://github.com/pnpm/spec/tree/master/lockfile + * we use the mapping present below to find the compatible pnpm version range for a given lockfileVersion + * + * the various terms used in the mapping are explained below: + * lowerConstraint : lowest pnpm version that can generate the lockfileVersion + * upperConstraint : highest pnpm version that can generate the lockfileVersion + * lowerBound : highest pnpm version that is less than the lowerConstraint + * upperBound : lowest pnpm version that is greater than the upperConstraint + * + * To handle future lockfileVersions, we need to: + * 1. add a upperBound and upperConstraint to the current latest lockfileVersion + * 2. add an object for the new lockfileVersion with lowerBound and lowerConstraint + * + * lockfileVersion from v6 on are strings */ - -const lockToPnpmVersionMapping = [ - { lockfileVersion: 6.0, lowerConstraint: '>=7.24.2' }, +const lockToPnpmVersionMapping: LockToPnpmVersionMapping[] = [ + { lockfileVersion: '9.0', lowerConstraint: '>=9' }, + { + lockfileVersion: '6.0', + lowerConstraint: '>=7.24.2', + upperConstraint: '<9', + }, { lockfileVersion: 5.4, lowerConstraint: '>=7', @@ -212,3 +223,9 @@ const lockToPnpmVersionMapping = [ upperConstraint: '<5.9.3', }, ]; + +type LockToPnpmVersionMapping = { + lockfileVersion: string | number; + lowerConstraint: string; + upperConstraint?: string; +}; diff --git a/lib/modules/manager/npm/post-update/rules.ts b/lib/modules/manager/npm/post-update/rules.ts index 116704478acffd..59380f7a616a96 100644 --- a/lib/modules/manager/npm/post-update/rules.ts +++ b/lib/modules/manager/npm/post-update/rules.ts @@ -2,7 +2,7 @@ import is from '@sindresorhus/is'; import * as hostRules from '../../../../util/host-rules'; import { regEx } from '../../../../util/regex'; import { toBase64 } from '../../../../util/string'; -import { validateUrl } from '../../../../util/url'; +import { isHttpUrl } from '../../../../util/url'; export interface HostRulesResult { additionalNpmrcContent: string[]; @@ -21,7 +21,7 @@ export function processHostRules(): HostRulesResult { if (hostRule.resolvedHost) { let uri = hostRule.matchHost; uri = - is.string(uri) && validateUrl(uri) + is.string(uri) && isHttpUrl(uri) ? uri.replace(regEx(/^https?:/), '') : // TODO: types (#22198) `//${uri}/`; diff --git a/lib/modules/manager/npm/post-update/utils.ts b/lib/modules/manager/npm/post-update/utils.ts index e799e3165285dc..6b3e246d11c27e 100644 --- a/lib/modules/manager/npm/post-update/utils.ts +++ b/lib/modules/manager/npm/post-update/utils.ts @@ -1,3 +1,4 @@ +import semver from 'semver'; import upath from 'upath'; import { logger } from '../../../../logger'; import { readLocalFile } from '../../../../util/fs'; @@ -35,7 +36,10 @@ export function getPackageManagerVersion( logger.debug( `Found ${name} constraint in package.json packageManager: ${version}`, ); - return version; + if (semver.valid(version)) { + return version; + } + return null; } if (pkg.engines?.[name]) { const version = pkg.engines[name]; diff --git a/lib/modules/manager/npm/post-update/yarn.spec.ts b/lib/modules/manager/npm/post-update/yarn.spec.ts index 791b648c3c0fe8..d1e01dc27a1e03 100644 --- a/lib/modules/manager/npm/post-update/yarn.spec.ts +++ b/lib/modules/manager/npm/post-update/yarn.spec.ts @@ -46,6 +46,9 @@ describe('modules/manager/npm/post-update/yarn', () => { beforeEach(() => { delete process.env.BUILDPACK; + delete process.env.HTTP_PROXY; + delete process.env.HTTPS_PROXY; + delete process.env.RENOVATE_X_YARN_PROXY; Fixtures.reset(); GlobalConfig.set({ localDir: '.', cacheDir: '/tmp/cache' }); removeDockerContainer.mockResolvedValue(); @@ -147,6 +150,41 @@ describe('modules/manager/npm/post-update/yarn', () => { expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); }); + it('sets http proxy', async () => { + process.env.HTTP_PROXY = 'http://proxy'; + process.env.HTTPS_PROXY = 'http://proxy'; + process.env.RENOVATE_X_YARN_PROXY = 'true'; + GlobalConfig.set({ + localDir: '.', + allowScripts: true, + cacheDir: '/tmp/cache', + }); + Fixtures.mock( + { + 'yarn.lock': 'package-lock-contents', + }, + 'some-dir', + ); + const execSnapshots = mockExecAll({ + stdout: '3.0.0', + stderr: '', + }); + const config = { + constraints: { + yarn: '3.0.0', + }, + }; + const res = await yarnHelper.generateLockFile('some-dir', {}, config); + expect(res.lockFile).toBe('package-lock-contents'); + expect(fixSnapshots(execSnapshots)).toMatchObject([ + { cmd: 'yarn config unset --home httpProxy' }, + { cmd: 'yarn config set --home httpProxy http://proxy' }, + { cmd: 'yarn config unset --home httpsProxy' }, + { cmd: 'yarn config set --home httpsProxy http://proxy' }, + {}, + ]); + }); + it('does not use global cache if zero install is detected', async () => { Fixtures.mock( { @@ -409,6 +447,53 @@ describe('modules/manager/npm/post-update/yarn', () => { expect(res.lockFile).toBe('package-lock-contents'); }); + it('supports packageManager url corepack', async () => { + process.env.CONTAINERBASE = 'true'; + GlobalConfig.set({ + localDir: '.', + binarySource: 'install', + cacheDir: '/tmp/cache', + }); + const yarnLockContents = `__metadata: + version: 6 + cacheKey: 8`; + Fixtures.mock( + { + 'package.json': + '{ "packageManager": "yarn@https://nexus-proxy.repo.local.company.net/nexus/content/groups/npm-all/@yarnpkg/cli-dist/-/cli-dist-3.7.0.tgz#sha224.a06723957ae0292e21f598a453" }', + 'yarn.lock': yarnLockContents, + }, + 'some-dir', + ); + mockedFunction(getPkgReleases).mockResolvedValueOnce({ + releases: [{ version: '0.10.0' }], + }); + const execSnapshots = mockExecAll({ + stdout: '2.1.0', + stderr: '', + }); + const config = partial>({ + managerData: { hasPackageManager: true }, + }); + const res = await yarnHelper.generateLockFile('some-dir', {}, config); + expect(execSnapshots).toMatchObject([ + { cmd: 'install-tool node 16.16.0', options: { cwd: 'some-dir' } }, + { cmd: 'install-tool corepack 0.10.0', options: { cwd: 'some-dir' } }, + { + cmd: 'yarn install --mode=update-lockfile', + options: { + cwd: 'some-dir', + env: { + YARN_ENABLE_GLOBAL_CACHE: '1', + YARN_ENABLE_IMMUTABLE_INSTALLS: 'false', + YARN_HTTP_TIMEOUT: '100000', + }, + }, + }, + ]); + expect(res.lockFile).toBe(yarnLockContents); + }); + it('supports corepack on grouping', async () => { process.env.CONTAINERBASE = 'true'; GlobalConfig.set({ diff --git a/lib/modules/manager/npm/post-update/yarn.ts b/lib/modules/manager/npm/post-update/yarn.ts index 64029f78157732..3334a8d6167419 100644 --- a/lib/modules/manager/npm/post-update/yarn.ts +++ b/lib/modules/manager/npm/post-update/yarn.ts @@ -210,6 +210,21 @@ export async function generateLockFile( commands.push(`yarn set version ${quote(yarnUpdate.newValue!)}`); } + if (process.env.RENOVATE_X_YARN_PROXY) { + if (process.env.HTTP_PROXY && !isYarn1) { + commands.push('yarn config unset --home httpProxy'); + commands.push( + `yarn config set --home httpProxy ${quote(process.env.HTTP_PROXY)}`, + ); + } + if (process.env.HTTPS_PROXY && !isYarn1) { + commands.push('yarn config unset --home httpsProxy'); + commands.push( + `yarn config set --home httpsProxy ${quote(process.env.HTTPS_PROXY)}`, + ); + } + } + // This command updates the lock file based on package.json commands.push(`yarn install${cmdOptions}`); diff --git a/lib/modules/manager/npm/readme.md b/lib/modules/manager/npm/readme.md index 9612cb9bf134a6..a5d8549e57ab5f 100644 --- a/lib/modules/manager/npm/readme.md +++ b/lib/modules/manager/npm/readme.md @@ -7,3 +7,20 @@ The following `depTypes` are currently supported by the npm manager : - `engines` : Renovate will update any `node`, `npm` and `yarn` version specified under `engines`. - `volta` : Renovate will update any `node`, `npm`, `pnpm` and `yarn` version specified under `volta`. - `packageManager` + +### Yarn + +#### Version Selection / Installation + +If Renovate detects a `packageManager` setting for Yarn in `package.json` then it will use Corepack to install Yarn. + +#### HTTP Proxy Support + +Yarn itself does not natively recognize/support the `HTTP_PROXY` and `HTTPS_PROXY` environment variables. + +You can configure `RENOVATE_X_YARN_PROXY=true` as an environment variable to enable configuring of Yarn proxy (e.g. if you cannot configure these proxy settings yourself in `~/.yarnrc.yml`). + +If set, and Renovate detects Yarn 2+, and one or both of those variables are present, then Renovate will run commands like `yarn config set --home httpProxy http://proxy` prior to executing `yarn install`. +This will result in the `~/.yarnrc.yml` file being created or modified with these settings, and the settings are not removed afterwards. + +Configuration/conversion of `NO_PROXY` to Yarn config is not supported. diff --git a/lib/modules/manager/npm/schema.ts b/lib/modules/manager/npm/schema.ts index 00847ca8c0210e..79d986fea78507 100644 --- a/lib/modules/manager/npm/schema.ts +++ b/lib/modules/manager/npm/schema.ts @@ -4,7 +4,7 @@ import { Json, LooseRecord } from '../../../util/schema-utils'; export const PackageManagerSchema = z .string() .transform((val) => val.split('@')) - .transform(([name, version]) => ({ name, version })); + .transform(([name, ...version]) => ({ name, version: version.join('@') })); export const PackageJsonSchema = z.object({ engines: LooseRecord(z.string()).optional(), diff --git a/lib/modules/manager/npm/update/dependency/index.spec.ts b/lib/modules/manager/npm/update/dependency/index.spec.ts index 2077d321849c79..e02760aadc42e4 100644 --- a/lib/modules/manager/npm/update/dependency/index.spec.ts +++ b/lib/modules/manager/npm/update/dependency/index.spec.ts @@ -375,5 +375,32 @@ describe('modules/manager/npm/update/dependency/index', () => { }); expect(testContent).toEqual(expected); }); + + it('handles pnpm.override dependency', () => { + const upgrade = { + depType: 'pnpm.overrides', + depName: 'typescript', + newValue: '0.60.0', + }; + const overrideDependencies = `{ + "pnpm": { + "overrides": { + "typescript": "0.0.5" + } + } + }`; + const expected = `{ + "pnpm": { + "overrides": { + "typescript": "0.60.0" + } + } + }`; + const testContent = npmUpdater.updateDependency({ + fileContent: overrideDependencies, + upgrade, + }); + expect(testContent).toEqual(expected); + }); }); }); diff --git a/lib/modules/manager/npm/update/dependency/index.ts b/lib/modules/manager/npm/update/dependency/index.ts index f6b164ab74cd36..b6e1ca0735a0b3 100644 --- a/lib/modules/manager/npm/update/dependency/index.ts +++ b/lib/modules/manager/npm/update/dependency/index.ts @@ -31,7 +31,11 @@ function renameObjKey( function replaceAsString( parsedContents: NpmPackage, fileContent: string, - depType: NpmDepType | 'dependenciesMeta' | 'packageManager', + depType: + | NpmDepType + | 'dependenciesMeta' + | 'packageManager' + | 'pnpm.overrides', depName: string, oldValue: string, newValue: string, @@ -39,6 +43,8 @@ function replaceAsString( ): string { if (depType === 'packageManager') { parsedContents[depType] = newValue; + } else if (depType === 'pnpm.overrides') { + parsedContents.pnpm!.overrides![depName] = newValue; } else if (depName === oldValue) { // The old value is the name of the dependency itself delete Object.assign(parsedContents[depType]!, { @@ -130,10 +136,9 @@ export function updateDependency({ } } if (upgrade.npmPackageAlias) { - // TODO: types (#22198) newValue = `npm:${upgrade.packageName}@${newValue}`; } - // TODO: types (#22198) + logger.debug(`npm.updateDependency(): ${depType}.${depName} = ${newValue}`); try { const parsedContents: NpmPackage = JSON.parse(fileContent); @@ -142,7 +147,6 @@ export function updateDependency({ let oldVersion: string | undefined; if (depType === 'packageManager') { oldVersion = parsedContents[depType]; - // TODO: types (#22198) newValue = `${depName}@${newValue}`; } else if (isOverrideObject(upgrade)) { overrideDepParents = managerData?.parents; @@ -157,8 +161,9 @@ export function updateDependency({ oldVersion = depObjectReference[overrideDepName]!; } } + } else if (depType === 'pnpm.overrides') { + oldVersion = parsedContents.pnpm?.overrides?.[depName]; } else { - // eslint-disable @typescript-eslint/no-unnecessary-type-assertion oldVersion = parsedContents[depType as NpmDepType]![depName] as string; } if (oldVersion === newValue) { @@ -262,6 +267,7 @@ export function updateDependency({ return null; } } + function overrideDepPosition( overrideBlock: OverrideDependency, parents: string[], diff --git a/lib/modules/manager/npm/utils.spec.ts b/lib/modules/manager/npm/utils.spec.ts index 039b520e8c641a..b154a83325b408 100644 --- a/lib/modules/manager/npm/utils.spec.ts +++ b/lib/modules/manager/npm/utils.spec.ts @@ -54,7 +54,7 @@ describe('modules/manager/npm/utils', () => { expect(lockFileComposed).toMatchSnapshot(); }); - it('adds trailing newline to match npms behaviour and avoid diffs', () => { + it('adds trailing newline to match npms behavior and avoid diffs', () => { const lockFile = Fixtures.get('lockfile-parsing/package-lock.json'); const { detectedIndent, lockFileParsed } = parseLockFile(lockFile); // TODO #22198 diff --git a/lib/modules/manager/nuget/artifacts.ts b/lib/modules/manager/nuget/artifacts.ts index 139c34a48fb915..88f4a96e486983 100644 --- a/lib/modules/manager/nuget/artifacts.ts +++ b/lib/modules/manager/nuget/artifacts.ts @@ -88,14 +88,14 @@ export async function updateArtifacts({ // https://github.com/NuGet/Home/wiki/Centrally-managing-NuGet-package-versions // https://github.com/microsoft/MSBuildSdks/tree/main/src/CentralPackageVersions - const isCentralManament = + const isCentralManagement = packageFileName === NUGET_CENTRAL_FILE || packageFileName === MSBUILD_CENTRAL_FILE || packageFileName.endsWith(`/${NUGET_CENTRAL_FILE}`) || packageFileName.endsWith(`/${MSBUILD_CENTRAL_FILE}`); if ( - !isCentralManament && + !isCentralManagement && !regEx(/(?:cs|vb|fs)proj$/i).test(packageFileName) ) { // This could be implemented in the future if necessary. @@ -111,7 +111,7 @@ export async function updateArtifacts({ const deps = await getDependentPackageFiles( packageFileName, - isCentralManament, + isCentralManagement, ); const packageFiles = deps.filter((d) => d.isLeaf).map((d) => d.name); diff --git a/lib/modules/manager/nuget/extract.spec.ts b/lib/modules/manager/nuget/extract.spec.ts index ca0aa58e026787..e70e37ee7aa4f8 100644 --- a/lib/modules/manager/nuget/extract.spec.ts +++ b/lib/modules/manager/nuget/extract.spec.ts @@ -60,6 +60,20 @@ describe('modules/manager/nuget/extract', () => { expect(res?.deps).toHaveLength(17); }); + it('extracts dependency with lower-case Version attribute', async () => { + const contents = codeBlock` + + + net8.0 + + + + + `; + const res = await extractPackageFile(contents, 'some.csproj', config); + expect(res?.deps).toHaveLength(1); + }); + it('extracts all dependencies from global packages file', async () => { const packageFile = 'packages.props'; const sample = Fixtures.get(packageFile); diff --git a/lib/modules/manager/nuget/extract.ts b/lib/modules/manager/nuget/extract.ts index bded64973137c0..624fa9a862d7fb 100644 --- a/lib/modules/manager/nuget/extract.ts +++ b/lib/modules/manager/nuget/extract.ts @@ -59,6 +59,7 @@ function extractDepsFromXml(xmlNode: XmlDocument): NugetPackageDependency[] { const depName = attr?.Include || attr?.Update; const version = attr?.Version ?? + attr?.version ?? child.valueWithPath('Version') ?? attr?.VersionOverride ?? child.valueWithPath('VersionOverride'); diff --git a/lib/modules/manager/nuget/package-tree.ts b/lib/modules/manager/nuget/package-tree.ts index 7dc29959c54f51..56b3f874ff6470 100644 --- a/lib/modules/manager/nuget/package-tree.ts +++ b/lib/modules/manager/nuget/package-tree.ts @@ -15,12 +15,12 @@ export const MSBUILD_CENTRAL_FILE = 'Packages.props'; */ export async function getDependentPackageFiles( packageFileName: string, - isCentralManament = false, + isCentralManagement = false, ): Promise { const packageFiles = await getAllPackageFiles(); const graph: ReturnType = Graph(); - if (isCentralManament) { + if (isCentralManagement) { graph.addNode(packageFileName); } @@ -33,7 +33,7 @@ export async function getDependentPackageFiles( for (const f of packageFiles) { graph.addNode(f); - if (isCentralManament && upath.dirname(f).startsWith(parentDir)) { + if (isCentralManagement && upath.dirname(f).startsWith(parentDir)) { graph.addEdge(packageFileName, f); } } @@ -70,7 +70,7 @@ export async function getDependentPackageFiles( const deps = new Map(); recursivelyGetDependentPackageFiles(packageFileName, graph, deps); - if (isCentralManament) { + if (isCentralManagement) { // remove props file, as we don't need it deps.delete(packageFileName); } diff --git a/lib/modules/manager/osgi/extract.spec.ts b/lib/modules/manager/osgi/extract.spec.ts index 7250b59f2dc457..cb07c5dc771043 100644 --- a/lib/modules/manager/osgi/extract.spec.ts +++ b/lib/modules/manager/osgi/extract.spec.ts @@ -1,107 +1,142 @@ +import { codeBlock } from 'common-tags'; import { extractPackageFile } from '.'; -const noArtifacts = `{ - "configurations": { - "org.apache.sling.jcr.davex.impl.servlets.SlingDavExServlet":{ - "alias":"/server" +const noArtifacts = codeBlock` + { + "configurations": { + "org.apache.sling.jcr.davex.impl.servlets.SlingDavExServlet": { + "alias": "/server" + } } } -}`; -const unsupportedFeatureVersion = `{ - "feature-resource-version": "2.0", - "bundles":[ +`; + +const unsupportedFeatureVersion = codeBlock` + { + "feature-resource-version": "2.0", + "bundles": [ { - "id":"commons-codec:commons-codec:1.15", - "start-order":"5" + "id": "commons-codec:commons-codec:1.15", + "start-order": "5" } - ] -}`; -const featureWithBundlesAsObjects = `{ - "feature-resource-version": "1.0", - "bundles":[ + ] + } +`; + +const featureWithBundlesAsObjects = codeBlock` + { + "feature-resource-version": "1.0", + "bundles": [ { - "id":"commons-codec:commons-codec:1.15", - "start-order":"5" + "id": "commons-codec:commons-codec:1.15", + "start-order": "5" }, { - "id":"commons-collections:commons-collections:3.2.2", - "start-order":"15" + "id": "commons-collections:commons-collections:3.2.2", + "start-order": "15" } - ] -}`; -const featureWithBundlesAsStrings = `{ - "bundles": [ - "org.apache.felix/org.apache.felix.scr/2.1.26", - "org.apache.felix/org.apache.felix.log/1.2.4" - ] -}`; -const featureWithComment = `{ - // comments are permitted - "bundles": [ "org.apache.aries:org.apache.aries.util:1.1.3" ] -}`; -const artifactsExtension = `{ - "content-packages:ARTIFACTS|true": [ + ] + } +`; + +const featureWithBundlesAsStrings = codeBlock` + { + "bundles": [ + "org.apache.felix/org.apache.felix.scr/2.1.26", + "org.apache.felix/org.apache.felix.log/1.2.4" + ] + } +`; + +const featureWithComment = codeBlock` + { + // comments are permitted + "bundles": [ + "org.apache.aries:org.apache.aries.util:1.1.3" + ] + } +`; + +const artifactsExtension = codeBlock` + { + "content-packages:ARTIFACTS|true": [ "com.day.cq:core.wcm.components.all:zip:2.21.0" - ] -}`; -const doubleSlashNotComment = `{ - "bundles":[ - { - "id":"com.h2database:h2-mvstore:2.1.214", - "start-order":"15" + ] + } +`; + +const doubleSlashNotComment = codeBlock` + { + "bundles": [ + { + "id": "com.h2database:h2-mvstore:2.1.214", + "start-order": "15" }, { - "id":"org.mongodb:mongo-java-driver:3.12.11", - "start-order":"15" - } - ], - "configurations":{ - "org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreService":{ - "db":"sling", - "mongouri":"mongodb://$[env:MONGODB_HOST;default=localhost]:$[env:MONGODB_PORT;type=Integer;default=27017]" - } + "id": "org.mongodb:mongo-java-driver:3.12.11", + "start-order": "15" + } + ], + "configurations": { + "org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreService": { + "db": "sling", + "mongouri": "mongodb://$[env:MONGODB_HOST;default=localhost]:$[env:MONGODB_PORT;type=Integer;default=27017]" + } + } } -}`; -const frameworkArtifact = `{ - "execution-environment:JSON|false":{ - "framework":{ - "id":"org.apache.felix:org.apache.felix.framework:7.0.5" +`; + +const frameworkArtifact = codeBlock` + { + "execution-environment:JSON|false": { + "framework": { + "id": "org.apache.felix:org.apache.felix.framework:7.0.5" } + } } -}`; -const versionWithVariable = `{ - "bundles":[ +`; + +const versionWithVariable = codeBlock` + { + "bundles": [ { - "id":"com.fasterxml.jackson.core:jackson-annotations:$\{jackson.version}", - "start-order":"20" + "id": "com.fasterxml.jackson.core:jackson-annotations:\${jackson.version}", + "start-order": "20" } - ] -}`; -const malformedDefinitions = `{ - "bundles":[ + ] + } +`; + +const malformedDefinitions = codeBlock` + { + "bundles": [ { - "#": "missing the 'id' attribute", - "not-id":"commons-codec:commons-codec:1.15" + "#": "missing the 'id' attribute", + "not-id": "commons-codec:commons-codec:1.15" }, { - "#": "too few parts in the GAV definition", - "id":"commons-codec:1.15" + "#": "too few parts in the GAV definition", + "id": "commons-codec:1.15" }, { - "#": "valid definition, should be extracted", - "id":"commons-codec:commons-codec:1.15" + "#": "valid definition, should be extracted", + "id": "commons-codec:commons-codec:1.15" } - ] -}`; -const invalidFeatureVersion = `{ - "feature-resource-version": "unknown", - "bundles":[ + ] + } +`; + +const invalidFeatureVersion = codeBlock` + { + "feature-resource-version": "unknown", + "bundles": [ { - "id":"commons-codec:commons-codec:1.15", - "start-order":"5" + "id": "commons-codec:commons-codec:1.15", + "start-order": "5" } - ] -}`; + ] + } +`; describe('modules/manager/osgi/extract', () => { describe('extractPackageFile()', () => { diff --git a/lib/modules/manager/pep621/__fixtures__/pyproject_pdm_lockedversion.lock b/lib/modules/manager/pep621/__fixtures__/pyproject_pdm_lockedversion.lock new file mode 100644 index 00000000000000..0919fa551afa66 --- /dev/null +++ b/lib/modules/manager/pep621/__fixtures__/pyproject_pdm_lockedversion.lock @@ -0,0 +1,147 @@ +# This file is @generated by PDM. +# It is not intended for manual editing. + +[metadata] +groups = ["default"] +strategy = ["cross_platform", "inherit_metadata"] +lock_version = "4.4.1" +content_hash = "sha256:19234b27486d54cb9ed56ee0f874329ee04094d0c5ac1e2cd37aee1a606a7693" + +[[package]] +name = "cffi" +version = "1.16.0" +requires_python = ">=3.8" +summary = "Foreign Function Interface for Python calling C code." +groups = ["default"] +dependencies = [ + "pycparser", +] +files = [ + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[[package]] +name = "cryptography" +version = "41.0.7" +requires_python = ">=3.7" +summary = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +groups = ["default"] +dependencies = [ + "cffi>=1.12", +] +files = [ + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, + {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, + {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, + {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, +] + +[[package]] +name = "deprecated" +version = "1.2.14" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +summary = "Python @deprecated decorator to deprecate old python classes, functions or methods." +groups = ["default"] +dependencies = [ + "wrapt<2,>=1.10", +] +files = [ + {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, + {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, +] + +[[package]] +name = "jwcrypto" +version = "1.4.1" +requires_python = ">= 3.6" +summary = "Implementation of JOSE Web standards" +groups = ["default"] +dependencies = [ + "cryptography>=2.3", + "deprecated", +] +files = [ + {file = "jwcrypto-1.4.1.tar.gz", hash = "sha256:9561d57f3e67f845bcc5bcb378d07e958d9b1ec2b7d9d2a9266296dabf27f4f4"}, +] + +[[package]] +name = "pycparser" +version = "2.21" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +summary = "C parser in Python" +groups = ["default"] +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + +[[package]] +name = "wrapt" +version = "1.16.0" +requires_python = ">=3.6" +summary = "Module for decorators, wrappers and monkey patching." +groups = ["default"] +files = [ + {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, + {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, + {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, + {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, + {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, + {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, + {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, +] diff --git a/lib/modules/manager/pep621/__fixtures__/pyproject_pdm_lockedversion.toml b/lib/modules/manager/pep621/__fixtures__/pyproject_pdm_lockedversion.toml new file mode 100644 index 00000000000000..a86d46f668b384 --- /dev/null +++ b/lib/modules/manager/pep621/__fixtures__/pyproject_pdm_lockedversion.toml @@ -0,0 +1,17 @@ +[build-system] +requires = ["pdm-backend"] +build-backend = "pdm.backend" + +[project] +name = "pep621-pdm" +version = "0.1.0" +description = "Default template for PDM package" +authors = [ + {name = "", email = ""}, +] +dependencies = [ + "jwcrypto>=1.4.1" +] +requires-python = ">=3.11" +readme = "README.md" +license = {text = "MIT"} diff --git a/lib/modules/manager/pep621/artifacts.spec.ts b/lib/modules/manager/pep621/artifacts.spec.ts index c3c3b9bd1bd89d..2c586b2ecdf292 100644 --- a/lib/modules/manager/pep621/artifacts.spec.ts +++ b/lib/modules/manager/pep621/artifacts.spec.ts @@ -127,7 +127,7 @@ requires-python = "<3.9" '&& ' + 'install-tool pdm v2.5.0 ' + '&& ' + - 'pdm update --no-sync dep1' + + 'pdm update --no-sync --update-eager dep1' + '"', options: { cwd: '/tmp/github/some/repo', diff --git a/lib/modules/manager/pep621/extract.spec.ts b/lib/modules/manager/pep621/extract.spec.ts index ff75c374d5e3fd..e7147e912b4b72 100644 --- a/lib/modules/manager/pep621/extract.spec.ts +++ b/lib/modules/manager/pep621/extract.spec.ts @@ -1,19 +1,22 @@ import { codeBlock } from 'common-tags'; import { Fixtures } from '../../../../test/fixtures'; +import { fs } from '../../../../test/util'; import { extractPackageFile } from '.'; +jest.mock('../../../util/fs'); + const pdmPyProject = Fixtures.get('pyproject_with_pdm.toml'); const pdmSourcesPyProject = Fixtures.get('pyproject_pdm_sources.toml'); describe('modules/manager/pep621/extract', () => { describe('extractPackageFile()', () => { - it('should return null for empty content', function () { - const result = extractPackageFile('', 'pyproject.toml'); + it('should return null for empty content', async () => { + const result = await extractPackageFile('', 'pyproject.toml'); expect(result).toBeNull(); }); - it('should return null for invalid toml', function () { - const result = extractPackageFile( + it('should return null for invalid toml', async () => { + const result = await extractPackageFile( codeBlock` [project] name = @@ -23,8 +26,8 @@ describe('modules/manager/pep621/extract', () => { expect(result).toBeNull(); }); - it('should return dependencies for valid content', function () { - const result = extractPackageFile(pdmPyProject, 'pyproject.toml'); + it('should return dependencies for valid content', async () => { + const result = await extractPackageFile(pdmPyProject, 'pyproject.toml'); expect(result).toMatchObject({ extractedConstraints: { @@ -177,8 +180,11 @@ describe('modules/manager/pep621/extract', () => { ]); }); - it('should return dependencies with overwritten pypi registryUrl', function () { - const result = extractPackageFile(pdmSourcesPyProject, 'pyproject.toml'); + it('should return dependencies with overwritten pypi registryUrl', async () => { + const result = await extractPackageFile( + pdmSourcesPyProject, + 'pyproject.toml', + ); expect(result?.deps).toEqual([ { @@ -239,8 +245,8 @@ describe('modules/manager/pep621/extract', () => { ]); }); - it('should return dependencies with original pypi registryUrl', function () { - const result = extractPackageFile( + it('should return dependencies with original pypi registryUrl', async () => { + const result = await extractPackageFile( codeBlock` [project] dependencies = [ @@ -270,9 +276,9 @@ describe('modules/manager/pep621/extract', () => { ]); }); - it('should extract dependencies from hatch environments', function () { + it('should extract dependencies from hatch environments', async () => { const hatchPyProject = Fixtures.get('pyproject_with_hatch.toml'); - const result = extractPackageFile(hatchPyProject, 'pyproject.toml'); + const result = await extractPackageFile(hatchPyProject, 'pyproject.toml'); expect(result?.deps).toEqual([ { @@ -322,7 +328,7 @@ describe('modules/manager/pep621/extract', () => { ]); }); - it('should extract project version', () => { + it('should extract project version', async () => { const content = codeBlock` [project] name = "test" @@ -330,11 +336,11 @@ describe('modules/manager/pep621/extract', () => { dependencies = [ "requests==2.30.0" ] `; - const res = extractPackageFile(content, 'pyproject.toml'); + const res = await extractPackageFile(content, 'pyproject.toml'); expect(res?.packageFileVersion).toBe('0.0.2'); }); - it('should extract dependencies from build-system.requires', function () { + it('should extract dependencies from build-system.requires', async () => { const content = codeBlock` [build-system] requires = ["hatchling==1.18.0", "setuptools==69.0.3"] @@ -345,7 +351,7 @@ describe('modules/manager/pep621/extract', () => { version = "0.0.2" dependencies = [ "requests==2.30.0" ] `; - const result = extractPackageFile(content, 'pyproject.toml'); + const result = await extractPackageFile(content, 'pyproject.toml'); expect(result?.deps).toEqual([ { @@ -374,5 +380,36 @@ describe('modules/manager/pep621/extract', () => { }, ]); }); + + it('should resolve lockedVersions from pdm.lock', async () => { + fs.readLocalFile.mockResolvedValue( + Fixtures.get('pyproject_pdm_lockedversion.lock'), + ); + + const res = await extractPackageFile( + Fixtures.get('pyproject_pdm_lockedversion.toml'), + 'pyproject.toml', + ); + expect(res).toMatchObject({ + extractedConstraints: { python: '>=3.11' }, + deps: [ + { + packageName: 'jwcrypto', + depName: 'jwcrypto', + datasource: 'pypi', + depType: 'project.dependencies', + currentValue: '>=1.4.1', + lockedVersion: '1.4.1', + }, + { + packageName: 'pdm-backend', + depName: 'pdm-backend', + datasource: 'pypi', + depType: 'build-system.requires', + skipReason: 'unspecified-version', + }, + ], + }); + }); }); }); diff --git a/lib/modules/manager/pep621/extract.ts b/lib/modules/manager/pep621/extract.ts index 66648663dcddaf..12a7c1803d4c37 100644 --- a/lib/modules/manager/pep621/extract.ts +++ b/lib/modules/manager/pep621/extract.ts @@ -13,11 +13,11 @@ import { parsePyProject, } from './utils'; -export function extractPackageFile( +export async function extractPackageFile( content: string, packageFile: string, _config?: ExtractConfig, -): PackageFileContent | null { +): Promise { logger.trace(`pep621.extractPackageFile(${packageFile})`); const deps: PackageDependency[] = []; @@ -54,6 +54,11 @@ export function extractPackageFile( let processedDeps = deps; for (const processor of processors) { processedDeps = processor.process(def, processedDeps); + processedDeps = await processor.extractLockedVersions( + def, + processedDeps, + packageFile, + ); } return processedDeps.length diff --git a/lib/modules/manager/pep621/processors/hatch.ts b/lib/modules/manager/pep621/processors/hatch.ts index b482cb5b18cfc0..0cb8cbe1494525 100644 --- a/lib/modules/manager/pep621/processors/hatch.ts +++ b/lib/modules/manager/pep621/processors/hatch.ts @@ -32,6 +32,14 @@ export class HatchProcessor implements PyProjectProcessor { return deps; } + extractLockedVersions( + project: PyProject, + deps: PackageDependency[], + packageFile: string, + ): Promise { + return Promise.resolve(deps); + } + updateArtifacts( updateArtifact: UpdateArtifact, project: PyProject, diff --git a/lib/modules/manager/pep621/processors/pdm.spec.ts b/lib/modules/manager/pep621/processors/pdm.spec.ts index 3e44d383d6b128..f53df3c2c241a1 100644 --- a/lib/modules/manager/pep621/processors/pdm.spec.ts +++ b/lib/modules/manager/pep621/processors/pdm.spec.ts @@ -89,7 +89,7 @@ describe('modules/manager/pep621/processors/pdm', () => { '&& ' + 'install-tool pdm v2.5.0 ' + '&& ' + - 'pdm update --no-sync dep1' + + 'pdm update --no-sync --update-eager dep1' + '"', }, ]); @@ -155,6 +155,7 @@ describe('modules/manager/pep621/processors/pdm', () => { depType: depTypes.pdmDevDependencies, }, { depName: 'group3/dep8', depType: depTypes.pdmDevDependencies }, + { depName: 'dep9', depType: depTypes.buildSystemRequires }, ]; const result = await processor.updateArtifacts( { @@ -176,16 +177,16 @@ describe('modules/manager/pep621/processors/pdm', () => { ]); expect(execSnapshots).toMatchObject([ { - cmd: 'pdm update --no-sync dep1 dep2', + cmd: 'pdm update --no-sync --update-eager dep1 dep2', }, { - cmd: 'pdm update --no-sync -G group1 dep3 dep4', + cmd: 'pdm update --no-sync --update-eager -G group1 dep3 dep4', }, { - cmd: 'pdm update --no-sync -dG group2 dep5 dep6', + cmd: 'pdm update --no-sync --update-eager -dG group2 dep5 dep6', }, { - cmd: 'pdm update --no-sync -dG group3 dep7 dep8', + cmd: 'pdm update --no-sync --update-eager -dG group3 dep7 dep8', }, ]); }); @@ -227,7 +228,7 @@ describe('modules/manager/pep621/processors/pdm', () => { ]); expect(execSnapshots).toMatchObject([ { - cmd: 'pdm update --no-sync', + cmd: 'pdm update --no-sync --update-eager', options: { cwd: '/tmp/github/some/repo/folder', }, diff --git a/lib/modules/manager/pep621/processors/pdm.ts b/lib/modules/manager/pep621/processors/pdm.ts index b4ead1f4bdb0a8..98afea80563e5c 100644 --- a/lib/modules/manager/pep621/processors/pdm.ts +++ b/lib/modules/manager/pep621/processors/pdm.ts @@ -5,6 +5,7 @@ import { logger } from '../../../../logger'; import { exec } from '../../../../util/exec'; import type { ExecOptions, ToolConstraint } from '../../../../util/exec/types'; import { getSiblingFileName, readLocalFile } from '../../../../util/fs'; +import { Result } from '../../../../util/result'; import { PypiDatasource } from '../../../datasource/pypi'; import type { PackageDependency, @@ -12,11 +13,11 @@ import type { UpdateArtifactsResult, Upgrade, } from '../../types'; -import type { PyProject } from '../schema'; +import { PdmLockfileSchema, type PyProject } from '../schema'; import { depTypes, parseDependencyGroupRecord } from '../utils'; import type { PyProjectProcessor } from './types'; -const pdmUpdateCMD = 'pdm update --no-sync'; +const pdmUpdateCMD = 'pdm update --no-sync --update-eager'; export class PdmProcessor implements PyProjectProcessor { process(project: PyProject, deps: PackageDependency[]): PackageDependency[] { @@ -53,6 +54,37 @@ export class PdmProcessor implements PyProjectProcessor { return deps; } + async extractLockedVersions( + project: PyProject, + deps: PackageDependency[], + packageFile: string, + ): Promise { + if ( + is.nullOrUndefined(project.tool?.pdm) && + project['build-system']?.['build-backend'] !== 'pdm.backend' + ) { + return Promise.resolve(deps); + } + + const lockFileName = getSiblingFileName(packageFile, 'pdm.lock'); + const lockFileContent = await readLocalFile(lockFileName, 'utf8'); + if (lockFileContent) { + const lockFileMapping = Result.parse( + lockFileContent, + PdmLockfileSchema.transform(({ lock }) => lock), + ).unwrapOrElse({}); + + for (const dep of deps) { + const packageName = dep.packageName; + if (packageName && packageName in lockFileMapping) { + dep.lockedVersion = lockFileMapping[packageName]; + } + } + } + + return Promise.resolve(deps); + } + async updateArtifacts( updateArtifact: UpdateArtifact, project: PyProject, @@ -155,6 +187,10 @@ function generateCMDs(updatedDeps: Upgrade[]): string[] { ); break; } + case depTypes.buildSystemRequires: + // build requirements are not locked in the lock files, no need to update. + // Reference: https://github.com/pdm-project/pdm/discussions/2869 + break; default: { addPackageToCMDRecord(packagesByCMD, pdmUpdateCMD, dep.packageName!); } diff --git a/lib/modules/manager/pep621/processors/types.ts b/lib/modules/manager/pep621/processors/types.ts index 16a382ce08f788..cc4c9861e58521 100644 --- a/lib/modules/manager/pep621/processors/types.ts +++ b/lib/modules/manager/pep621/processors/types.ts @@ -18,4 +18,10 @@ export interface PyProjectProcessor { * @param deps List of already extracted/processed dependencies */ process(project: PyProject, deps: PackageDependency[]): PackageDependency[]; + + extractLockedVersions( + project: PyProject, + deps: PackageDependency[], + packageFile: string, + ): Promise; } diff --git a/lib/modules/manager/pep621/schema.ts b/lib/modules/manager/pep621/schema.ts index 212a1301c1b257..5edcd8b8843d4d 100644 --- a/lib/modules/manager/pep621/schema.ts +++ b/lib/modules/manager/pep621/schema.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { LooseArray, Toml } from '../../../util/schema-utils'; export type PyProject = z.infer; @@ -19,6 +20,7 @@ export const PyProjectSchema = z.object({ 'build-system': z .object({ requires: DependencyListSchema, + 'build-backend': z.string().optional(), }) .optional(), tool: z @@ -55,3 +57,20 @@ export const PyProjectSchema = z.object({ }) .optional(), }); + +export const PdmLockfileSchema = Toml.pipe( + z.object({ + package: LooseArray( + z.object({ + name: z.string(), + version: z.string(), + }), + ), + }), +) + .transform(({ package: pkg }) => + Object.fromEntries( + pkg.map(({ name, version }): [string, string] => [name, version]), + ), + ) + .transform((lock) => ({ lock })); diff --git a/lib/modules/manager/pip-compile/common.spec.ts b/lib/modules/manager/pip-compile/common.spec.ts index 71fbf05bc31552..8d39bc116f4a79 100644 --- a/lib/modules/manager/pip-compile/common.spec.ts +++ b/lib/modules/manager/pip-compile/common.spec.ts @@ -1,5 +1,6 @@ import { mockDeep } from 'jest-mock-extended'; import { hostRules } from '../../../../test/util'; +import { logger } from '../../../logger'; import { allowedPipOptions, extractHeaderCommand, @@ -23,7 +24,11 @@ describe('modules/manager/pip-compile/common', () => { describe('extractHeaderCommand()', () => { it.each([ '-v', + '--all-extras', + `--allow-unsafe`, '--generate-hashes', + `--no-emit-index-url`, + `--strip-extras`, '--resolver=backtracking', '--resolver=legacy', '--output-file=reqs.txt', @@ -35,6 +40,7 @@ describe('modules/manager/pip-compile/common', () => { 'reqs.txt', ), ).toBeObject(); + expect(logger.warn).toHaveBeenCalledTimes(0); }); it.each(['--resolver', '--output-file reqs.txt', '--extra = jupyter'])( diff --git a/lib/modules/manager/pip-compile/common.ts b/lib/modules/manager/pip-compile/common.ts index b2771c9547ed9b..0d3600d57f92e3 100644 --- a/lib/modules/manager/pip-compile/common.ts +++ b/lib/modules/manager/pip-compile/common.ts @@ -11,7 +11,7 @@ import { regEx } from '../../../util/regex'; import type { PackageFileContent, UpdateArtifactsConfig } from '../types'; import type { PipCompileArgs } from './types'; -export function getPythonConstraint( +export function getPythonVersionConstraint( config: UpdateArtifactsConfig, ): string | undefined | null { const { constraints = {} } = config; @@ -24,8 +24,9 @@ export function getPythonConstraint( return undefined; } -// TODO(not7cd): rename to getPipToolsVersionConstraint, as constraints have their meaning in pip -export function getPipToolsConstraint(config: UpdateArtifactsConfig): string { +export function getPipToolsVersionConstraint( + config: UpdateArtifactsConfig, +): string { const { constraints = {} } = config; const { pipTools } = constraints; @@ -41,8 +42,8 @@ export async function getExecOptions( cwd: string, extraEnv: ExtraEnv, ): Promise { - const constraint = getPythonConstraint(config); - const pipToolsConstraint = getPipToolsConstraint(config); + const constraint = getPythonVersionConstraint(config); + const pipToolsConstraint = getPipToolsVersionConstraint(config); const execOptions: ExecOptions = { cwd: ensureLocalPath(cwd), docker: {}, @@ -84,6 +85,7 @@ export const optionsWithArguments = [ ]; export const allowedPipOptions = [ '-v', + '--all-extras', '--allow-unsafe', '--generate-hashes', '--no-emit-index-url', @@ -159,7 +161,7 @@ export function extractHeaderCommand( result.indexUrl = value; // TODO: add to secrets? next PR } else { - logger.warn(`pip-compile: option ${arg} not handled`); + logger.debug({ option }, `pip-compile: option not handled`); } continue; } @@ -171,8 +173,12 @@ export function extractHeaderCommand( result.emitIndexUrl = true; continue; } + if (arg === '--all-extras') { + result.allExtras = true; + continue; + } - logger.warn(`pip-compile: option ${arg} not handled`); + logger.debug({ option: arg }, `pip-compile: option not handled`); } logger.trace( { diff --git a/lib/modules/manager/pip-compile/extract.spec.ts b/lib/modules/manager/pip-compile/extract.spec.ts index bd45db12fff388..daabb05c5edb42 100644 --- a/lib/modules/manager/pip-compile/extract.spec.ts +++ b/lib/modules/manager/pip-compile/extract.spec.ts @@ -246,26 +246,32 @@ describe('modules/manager/pip-compile/extract', () => { }); it('return sorted package files', async () => { - fs.readLocalFile.mockResolvedValueOnce( - getSimpleRequirementsFile('pip-compile --output-file=4.txt 3.in', [ - 'foo==1.0.1', - ]), - ); - fs.readLocalFile.mockResolvedValueOnce('-r 2.txt\nfoo'); - fs.readLocalFile.mockResolvedValueOnce( - getSimpleRequirementsFile('pip-compile --output-file=2.txt 1.in', [ - 'foo==1.0.1', - ]), - ); - fs.readLocalFile.mockResolvedValueOnce('foo'); + fs.readLocalFile.mockImplementation((name): any => { + if (name === '1.in') { + return 'foo'; + } else if (name === '2.txt') { + return getSimpleRequirementsFile( + 'pip-compile --output-file=2.txt 1.in', + ['foo==1.0.1'], + ); + } else if (name === '3.in') { + return '-r 2.txt\nfoo'; + } else if (name === '4.txt') { + return getSimpleRequirementsFile( + 'pip-compile --output-file=4.txt 3.in', + ['foo==1.0.1'], + ); + } + return null; + }); const lockFiles = ['4.txt', '2.txt']; const packageFiles = await extractAllPackageFiles({}, lockFiles); expect(packageFiles).toBeDefined(); expect(packageFiles?.map((p) => p.packageFile)).toEqual(['1.in', '3.in']); - expect(packageFiles?.map((p) => p.lockFiles!.pop())).toEqual([ - '2.txt', - '4.txt', + expect(packageFiles?.map((p) => p.lockFiles)).toEqual([ + ['2.txt', '4.txt'], + ['4.txt'], ]); }); @@ -359,4 +365,241 @@ describe('modules/manager/pip-compile/extract', () => { 'pip-compile: dependency not found in lock file', ); }); + + it('adds transitive dependency to deps in package file', async () => { + fs.readLocalFile.mockResolvedValueOnce( + getSimpleRequirementsFile( + 'pip-compile --output-file=requirements.txt requirements.in', + ['friendly-bard==1.0.1', 'bards-friend==1.0.0'], + ), + ); + fs.readLocalFile.mockResolvedValueOnce('FrIeNdLy-._.-bArD>=1.0.0'); + + const lockFiles = ['requirements.txt']; + const packageFiles = await extractAllPackageFiles({}, lockFiles); + expect(packageFiles).toBeDefined(); + const packageFile = packageFiles!.pop(); + expect(packageFile!.deps).toHaveLength(2); + expect(packageFile!.deps[1]).toEqual({ + datasource: 'pypi', + depType: 'indirect', + depName: 'bards-friend', + lockedVersion: '1.0.0', + enabled: false, + }); + }); + + it('handles -r reference to another input file', async () => { + fs.readLocalFile.mockImplementation((name): any => { + if (name === '1.in') { + return 'foo'; + } else if (name === '2.txt') { + return getSimpleRequirementsFile( + 'pip-compile --output-file=2.txt 1.in', + ['foo==1.0.1'], + ); + } else if (name === '3.in') { + return '-r 1.in\nfoo'; + } else if (name === '4.txt') { + return getSimpleRequirementsFile( + 'pip-compile --output-file=4.txt 3.in', + ['foo==1.0.1'], + ); + } + return null; + }); + + const lockFiles = ['4.txt', '2.txt']; + const packageFiles = await extractAllPackageFiles({}, lockFiles); + expect(packageFiles?.map((p) => p.lockFiles)).toEqual([ + ['2.txt', '4.txt'], + ['4.txt'], + ]); + }); + + it('handles transitive -r references', async () => { + fs.readLocalFile.mockImplementation((name): any => { + if (name === '1.in') { + return 'foo'; + } else if (name === '2.txt') { + return getSimpleRequirementsFile( + 'pip-compile --output-file=2.txt 1.in', + ['foo==1.0.1'], + ); + } else if (name === '3.in') { + return '-r 1.in\nfoo'; + } else if (name === '4.txt') { + return getSimpleRequirementsFile( + 'pip-compile --output-file=4.txt 3.in', + ['foo==1.0.1'], + ); + } else if (name === '5.in') { + return '-r 4.txt\nfoo'; + } else if (name === '6.txt') { + return getSimpleRequirementsFile( + 'pip-compile --output-file=6.txt 5.in', + ['foo==1.0.1'], + ); + } + return null; + }); + + const lockFiles = ['4.txt', '2.txt', '6.txt']; + const packageFiles = await extractAllPackageFiles({}, lockFiles); + expect(packageFiles?.map((p) => p.lockFiles)).toEqual([ + ['2.txt', '4.txt', '6.txt'], + ['4.txt', '6.txt'], + ['6.txt'], + ]); + }); + + it('warns on -r reference to failed file', async () => { + fs.readLocalFile.mockImplementation((name): any => { + if (name === 'reqs-no-headers.txt') { + return Fixtures.get('requirementsNoHeaders.txt'); + } else if (name === '1.in') { + return '-r reqs-no-headers.txt\nfoo'; + } else if (name === '2.txt') { + return getSimpleRequirementsFile( + 'pip-compile --output-file=2.txt 1.in', + ['foo==1.0.1'], + ); + } + return null; + }); + + const lockFiles = ['reqs-no-headers.txt', '2.txt']; + const packageFiles = await extractAllPackageFiles({}, lockFiles); + expect(packageFiles?.map((p) => p.lockFiles)).toEqual([['2.txt']]); + expect(logger.warn).toHaveBeenCalledWith( + 'pip-compile: 1.in references reqs-no-headers.txt which does not appear to be a requirements file managed by pip-compile', + ); + }); + + it('warns on -r reference to requirements file not managed by pip-compile', async () => { + fs.readLocalFile.mockImplementation((name): any => { + if (name === '1.in') { + return '-r unmanaged-file.txt\nfoo'; + } else if (name === '2.txt') { + return getSimpleRequirementsFile( + 'pip-compile --output-file=2.txt 1.in', + ['foo==1.0.1'], + ); + } + return null; + }); + + const lockFiles = ['2.txt']; + const packageFiles = await extractAllPackageFiles({}, lockFiles); + expect(packageFiles?.map((p) => p.lockFiles)).toEqual([['2.txt']]); + expect(logger.warn).toHaveBeenCalledWith( + 'pip-compile: 1.in references unmanaged-file.txt which does not appear to be a requirements file managed by pip-compile', + ); + }); + + it('handles duplicate -r dependencies', async () => { + fs.readLocalFile.mockImplementation((name): any => { + if (name === '1.in') { + return 'foo'; + } else if (name === '1.txt') { + return getSimpleRequirementsFile( + 'pip-compile --output-file=1.txt 1.in', + ['foo==1.0.1'], + ); + } else if (name === '2.in') { + return '-r 1.txt\nbar'; + } else if (name === '2.txt') { + return getSimpleRequirementsFile( + 'pip-compile --output-file=2.txt 2.in', + ['foo==1.0.1', 'bar==2.0.0'], + ); + } else if (name === '3.in') { + return '-r 1.txt\nbaz'; + } else if (name === '3.txt') { + return getSimpleRequirementsFile( + 'pip-compile --output-file=3.txt 3.in', + ['foo==1.0.1', 'baz==2.0.0'], + ); + } else if (name === '4.in') { + return '-r 2.txt\n-r 3.txt\nqux'; + } else if (name === '4.txt') { + return getSimpleRequirementsFile( + 'pip-compile --output-file=4.txt 4.in', + ['foo==1.0.1', 'bar==2.0.0', 'baz==2.0.0', 'qux==1.2.3'], + ); + } + return null; + }); + + const lockFiles = ['1.txt', '2.txt', '3.txt', '4.txt']; + const packageFiles = await extractAllPackageFiles({}, lockFiles); + expect(packageFiles?.map((p) => p.lockFiles)).toEqual([ + ['1.txt', '2.txt', '3.txt', '4.txt'], + ['3.txt', '4.txt'], + ['2.txt', '4.txt'], + ['4.txt'], + ]); + }); + + it('handles -r dependency on lock file with multiple input files', async () => { + fs.readLocalFile.mockImplementation((name): any => { + if (name === '1.in') { + return 'foo'; + } else if (name === '2.in') { + return 'bar'; + } else if (name === 'multi_input.txt') { + return getSimpleRequirementsFile( + 'pip-compile --output-file=multi_input.txt 1.in 2.in', + ['foo==1.0.1', 'bar==2.0.0'], + ); + } else if (name === '3.in') { + return '-r multi_input.txt\nbaz'; + } else if (name === 'dash_r.txt') { + return getSimpleRequirementsFile( + 'pip-compile --output-file=dash_r.txt 3.in', + ['foo==1.0.1', 'bar==2.0.0', 'baz==2.0.0'], + ); + } + return null; + }); + + const lockFiles = ['multi_input.txt', 'dash_r.txt']; + const packageFiles = await extractAllPackageFiles({}, lockFiles); + expect(packageFiles?.map((p) => p.lockFiles)).toEqual([ + ['multi_input.txt', 'dash_r.txt'], + ['multi_input.txt', 'dash_r.txt'], + ['dash_r.txt'], + ]); + }); + + it('handles -r dependency on input file that is also used to generate lock file with multiple inputs', async () => { + fs.readLocalFile.mockImplementation((name): any => { + if (name === '1.in') { + return 'foo'; + } else if (name === '2.in') { + return 'bar'; + } else if (name === 'multi_input.txt') { + return getSimpleRequirementsFile( + 'pip-compile --output-file=multi_input.txt 1.in 2.in', + ['foo==1.0.1', 'bar==2.0.0'], + ); + } else if (name === '3.in') { + return '-r 1.in\nbaz'; + } else if (name === 'dash_r.txt') { + return getSimpleRequirementsFile( + 'pip-compile --output-file=dash_r.txt 3.in', + ['foo==1.0.1', 'baz==2.0.0'], + ); + } + return null; + }); + + const lockFiles = ['multi_input.txt', 'dash_r.txt']; + const packageFiles = await extractAllPackageFiles({}, lockFiles); + expect(packageFiles?.map((p) => p.lockFiles)).toEqual([ + ['multi_input.txt'], + ['multi_input.txt', 'dash_r.txt'], + ['dash_r.txt'], + ]); + }); }); diff --git a/lib/modules/manager/pip-compile/extract.ts b/lib/modules/manager/pip-compile/extract.ts index ed011867f7ad02..977b5c7c66d40b 100644 --- a/lib/modules/manager/pip-compile/extract.ts +++ b/lib/modules/manager/pip-compile/extract.ts @@ -2,10 +2,15 @@ import upath from 'upath'; import { logger } from '../../../logger'; import { readLocalFile } from '../../../util/fs'; import { ensureLocalPath } from '../../../util/fs/util'; -import { normalizeDepName } from '../../datasource/pypi/common'; +import { normalizePythonDepName } from '../../datasource/pypi/common'; import { extractPackageFile as extractRequirementsFile } from '../pip_requirements/extract'; import { extractPackageFile as extractSetupPyFile } from '../pip_setup'; -import type { ExtractConfig, PackageFile, PackageFileContent } from '../types'; +import type { + ExtractConfig, + PackageDependency, + PackageFile, + PackageFileContent, +} from '../types'; import { extractHeaderCommand } from './common'; import type { DependencyBetweenFiles, @@ -70,6 +75,7 @@ export async function extractAllPackageFiles( const lockFileArgs = new Map(); const depsBetweenFiles: DependencyBetweenFiles[] = []; const packageFiles = new Map(); + const lockFileSources = new Map(); for (const fileMatch of fileMatches) { const fileContent = await readLocalFile(fileMatch, 'utf8'); if (!fileContent) { @@ -132,7 +138,12 @@ export async function extractAllPackageFiles( logger.debug( `pip-compile: ${packageFile} used in multiple output files`, ); - packageFiles.get(packageFile)!.lockFiles!.push(fileMatch); + const existingPackageFile = packageFiles.get(packageFile)!; + existingPackageFile.lockFiles!.push(fileMatch); + extendWithIndirectDeps(existingPackageFile, lockedDeps); + const source = lockFileSources.get(fileMatch) ?? []; + source.push(existingPackageFile); + lockFileSources.set(fileMatch, source); continue; } const content = await readLocalFile(packageFile, 'utf8'); @@ -168,8 +179,8 @@ export async function extractAllPackageFiles( for (const dep of packageFileContent.deps) { const lockedVersion = lockedDeps?.find( (lockedDep) => - normalizeDepName(lockedDep.depName!) === - normalizeDepName(dep.depName!), + normalizePythonDepName(lockedDep.depName!) === + normalizePythonDepName(dep.depName!), )?.currentVersion; if (lockedVersion) { dep.lockedVersion = lockedVersion; @@ -180,11 +191,16 @@ export async function extractAllPackageFiles( ); } } - packageFiles.set(packageFile, { + extendWithIndirectDeps(packageFileContent, lockedDeps); + const newPackageFile: PackageFile = { ...packageFileContent, lockFiles: [fileMatch], packageFile, - }); + }; + packageFiles.set(packageFile, newPackageFile); + const source = lockFileSources.get(fileMatch) ?? []; + source.push(newPackageFile); + lockFileSources.set(fileMatch, source); } else { logger.warn( { packageFile }, @@ -200,9 +216,82 @@ export async function extractAllPackageFiles( depsBetweenFiles, packageFiles, ); + + // This needs to go in reverse order to handle transitive dependencies + for (const packageFile of [...result].reverse()) { + for (const reqFile of packageFile.managerData?.requirementsFiles ?? []) { + let sourceFiles: PackageFile[] | undefined = undefined; + if (fileMatches.includes(reqFile)) { + sourceFiles = lockFileSources.get(reqFile); + } else if (packageFiles.has(reqFile)) { + sourceFiles = [packageFiles.get(reqFile)!]; + } + if (!sourceFiles) { + logger.warn( + `pip-compile: ${packageFile.packageFile} references ${reqFile} which does not appear to be a requirements file managed by pip-compile`, + ); + continue; + } + // These get reversed before merging so that we keep the last instance of any common + // lock files, since a file that -r includes multiple lock files needs to be updated after + // all of the lock files it includes + const files = new Set([...packageFile.lockFiles!].reverse()); + for (const sourceFile of sourceFiles) { + const merged = new Set(files); + for (const lockFile of [...sourceFile.lockFiles!].reverse()) { + merged.add(lockFile); + } + sourceFile.lockFiles = Array.from(merged).reverse(); + } + } + } logger.debug( 'pip-compile: dependency graph:\n' + generateMermaidGraph(depsBetweenFiles, lockFileArgs), ); return result; } + +function extendWithIndirectDeps( + packageFileContent: PackageFileContent, + lockedDeps: PackageDependency[], +): void { + for (const lockedDep of lockedDeps) { + if ( + !packageFileContent.deps.find( + (dep) => + normalizePythonDepName(lockedDep.depName!) === + normalizePythonDepName(dep.depName!), + ) + ) { + packageFileContent.deps.push(indirectDep(lockedDep)); + } + } +} + +/** + * As indirect dependecies don't exist in the package file, we need to + * create them from the lock file. + * + * By removing currentValue and currentVersion, we ensure that they + * are handled like unconstrained dependencies with locked version. + * Such packages are updated when their update strategy + * is set to 'update-lockfile', + * see: lib/workers/repository/process/lookup/index.ts. + * + * By disabling them by default, we won't create noise by updating them. + * Unless they have vulnerability alert, then they are forced to be updated. + * @param dep dependency extracted from lock file (requirements.txt) + * @returns unconstrained dependency with locked version + */ +function indirectDep(dep: PackageDependency): PackageDependency { + const result = { + ...dep, + lockedVersion: dep.currentVersion, + depType: 'indirect', + enabled: false, + }; + delete result.currentValue; + delete result.currentVersion; + return result; +} diff --git a/lib/modules/manager/pip-compile/readme.md b/lib/modules/manager/pip-compile/readme.md index d7ca06f5477daa..24324ec67403cc 100644 --- a/lib/modules/manager/pip-compile/readme.md +++ b/lib/modules/manager/pip-compile/readme.md @@ -83,3 +83,9 @@ Renovate reads the `requirements.txt` file and extracts these `pip-compile` argu - `--output-file` All other allowed `pip-compile` arguments will be passed over without modification. + +### Transitive / indirect dependencies + +This manager detects dependencies that only appear in lock files. +They are disabled by default but can be forced to enable by vulnerability alerts. +They will be upgraded with `--upgrade-package` option. diff --git a/lib/modules/manager/pip-compile/types.ts b/lib/modules/manager/pip-compile/types.ts index 4d7ad9ad242ebd..c496e01c886a7d 100644 --- a/lib/modules/manager/pip-compile/types.ts +++ b/lib/modules/manager/pip-compile/types.ts @@ -1,4 +1,4 @@ -// managers supported by pip-tools Python package +// managers supported by pip-tools mapped to Renovate's internal names export type SupportedManagers = | 'pip_requirements' | 'pip_setup' @@ -11,6 +11,7 @@ export interface PipCompileArgs { isCustomCommand: boolean; constraintsFiles?: string[]; extra?: string[]; + allExtras?: boolean; extraIndexUrl?: string[]; indexUrl?: string; noEmitIndexUrl?: boolean; diff --git a/lib/modules/manager/pip_requirements/extract.spec.ts b/lib/modules/manager/pip_requirements/extract.spec.ts index 84eface817b47f..76932dce197188 100644 --- a/lib/modules/manager/pip_requirements/extract.spec.ts +++ b/lib/modules/manager/pip_requirements/extract.spec.ts @@ -239,5 +239,43 @@ some-package==0.3.1`; ], }); }); + + it('extracts a file with only --index-url flags', () => { + const res = extractPackageFile('--index-url https://example.com/pypi'); + expect(res).toMatchObject({ + deps: [], + registryUrls: ['https://example.com/pypi'], + }); + }); + + it('extracts a file with only --extra-index-url flags', () => { + const res = extractPackageFile( + '--extra-index-url https://example.com/pypi', + ); + expect(res).toMatchObject({ + deps: [], + additionalRegistryUrls: ['https://example.com/pypi'], + }); + }); + + it('extracts a file with only -r flags', () => { + const res = extractPackageFile('-r requirements-other.txt'); + expect(res).toMatchObject({ + deps: [], + managerData: { + requirementsFiles: ['requirements-other.txt'], + }, + }); + }); + + it('extracts a file with only -c flags', () => { + const res = extractPackageFile('-c constraints.txt'); + expect(res).toMatchObject({ + deps: [], + managerData: { + constraintsFiles: ['constraints.txt'], + }, + }); + }); }); }); diff --git a/lib/modules/manager/pip_requirements/extract.ts b/lib/modules/manager/pip_requirements/extract.ts index bf29df35775b1a..0f7818f49ea56c 100644 --- a/lib/modules/manager/pip_requirements/extract.ts +++ b/lib/modules/manager/pip_requirements/extract.ts @@ -131,7 +131,13 @@ export function extractPackageFile( return dep; }) .filter(is.truthy); - if (!deps.length) { + if ( + !deps.length && + !registryUrls.length && + !additionalRegistryUrls.length && + !additionalRequirementsFiles.length && + !additionalConstraintsFiles.length + ) { return null; } const res: PackageFileContent = { deps }; diff --git a/lib/modules/manager/pip_requirements/index.spec.ts b/lib/modules/manager/pip_requirements/index.spec.ts new file mode 100644 index 00000000000000..3ebe809fc48b54 --- /dev/null +++ b/lib/modules/manager/pip_requirements/index.spec.ts @@ -0,0 +1,13 @@ +import { defaultConfig } from '.'; + +describe('modules/manager/pip_requirements/index', () => { + it('default config file pattern', () => { + const reg = new RegExp(defaultConfig.fileMatch[0]); + + expect(reg.test('requirements.txt')).toBe(true); + expect(reg.test('requirements-dev.txt')).toBe(true); + expect(reg.test('requirements.dev.txt')).toBe(true); + expect(reg.test('requirements-dev.pip')).toBe(true); + expect(reg.test('requirements.dev.pip')).toBe(true); + }); +}); diff --git a/lib/modules/manager/pip_requirements/index.ts b/lib/modules/manager/pip_requirements/index.ts index 537d4a510fef25..389ccacb021ea3 100644 --- a/lib/modules/manager/pip_requirements/index.ts +++ b/lib/modules/manager/pip_requirements/index.ts @@ -6,7 +6,7 @@ export { updateArtifacts } from './artifacts'; export { extractPackageFile } from './extract'; export const defaultConfig = { - fileMatch: ['(^|/)[\\w-]*requirements(-\\w+)?\\.(txt|pip)$'], + fileMatch: ['(^|/)[\\w-]*requirements([-.]\\w+)?\\.(txt|pip)$'], }; export const categories: Category[] = ['python']; diff --git a/lib/modules/manager/pip_requirements/readme.md b/lib/modules/manager/pip_requirements/readme.md index 200813c1c65398..f8de13edc2296c 100644 --- a/lib/modules/manager/pip_requirements/readme.md +++ b/lib/modules/manager/pip_requirements/readme.md @@ -1,2 +1,2 @@ Supports `requirements.txt` and `requirements.pip` files. -The default file pattern is fairly flexible, to try to catch similarly named ones too (eg `requirements-*.txt` and `requirements-*.pip`) but may be extended/changed. +The default file pattern is fairly flexible, to try to catch similarly named ones too (eg `requirements-*.txt` `requirements.*.txt` `requirements-*.pip` and `requirements.*.pip`) but may be extended/changed. diff --git a/lib/modules/manager/pip_setup/readme.md b/lib/modules/manager/pip_setup/readme.md index b3efeec01524c1..0b7b0442bbf15d 100644 --- a/lib/modules/manager/pip_setup/readme.md +++ b/lib/modules/manager/pip_setup/readme.md @@ -1 +1 @@ -`setup.py` files are parsed by calling out to `python` and then using a mock to detect imported modules. +Renovate uses a JavaScript-based parser to process the `pip_setup` files. diff --git a/lib/modules/manager/pipenv/__fixtures__/Pipfile1 b/lib/modules/manager/pipenv/__fixtures__/Pipfile1 index 2780a7a308442a..45643ed6681b0b 100644 --- a/lib/modules/manager/pipenv/__fixtures__/Pipfile1 +++ b/lib/modules/manager/pipenv/__fixtures__/Pipfile1 @@ -10,13 +10,22 @@ name = "private-pypi" [packages] some-package = "==0.3.1" +"splinter[django]" = "==3.43.1" +"requests[django][security][something]" = "==1.1.1" some-other-package = "==1.0.0" "_invalid-package" = "==1.0.0" invalid-version = "==0 0" pytest-benchmark = {version = "==1.0.0", extras = ["histogram"]} +[container] +container-specific-package = "==1.2.3" + +[function] +function-specific-package = "==2.3.4" + [dev-packages] dev-package = "==0.1.0" [requires] python_version = "3.6" +python_full_version = "3.6.2" diff --git a/lib/modules/manager/pipenv/artifacts.spec.ts b/lib/modules/manager/pipenv/artifacts.spec.ts index ca5692fcfd2756..a3975ef6c31603 100644 --- a/lib/modules/manager/pipenv/artifacts.spec.ts +++ b/lib/modules/manager/pipenv/artifacts.spec.ts @@ -12,18 +12,23 @@ import { } from '../../../../test/util'; import { GlobalConfig } from '../../../config/global'; import type { RepoGlobalConfig } from '../../../config/types'; +import { logger } from '../../../logger'; import * as docker from '../../../util/exec/docker'; +import type { ExtraEnv, Opt } from '../../../util/exec/types'; import type { StatusResult } from '../../../util/git/types'; import { find as _find } from '../../../util/host-rules'; -import { getPkgReleases as _getPkgReleases } from '../../datasource'; import * as _datasource from '../../datasource'; import type { UpdateArtifactsConfig } from '../types'; +import { + addExtraEnvVariable, + extractEnvironmentVariableName, + getMatchingHostRule, +} from './artifacts'; import type { PipfileLockSchema } from './schema'; import { updateArtifacts } from '.'; const datasource = mocked(_datasource); const find = mockedFunction(_find); -const getPkgReleases = mockedFunction(_getPkgReleases); jest.mock('../../../util/exec/env'); jest.mock('../../../util/git'); @@ -71,8 +76,10 @@ describe('modules/manager/pipenv/artifacts', () => { }; // python - getPkgReleases.mockResolvedValueOnce({ + datasource.getPkgReleases.mockResolvedValueOnce({ releases: [ + { version: '3.6.2' }, + { version: '3.6.5' }, { version: '3.7.6' }, { version: '3.8.5' }, { version: '3.9.1' }, @@ -81,7 +88,7 @@ describe('modules/manager/pipenv/artifacts', () => { }); // pipenv - getPkgReleases.mockResolvedValueOnce({ + datasource.getPkgReleases.mockResolvedValueOnce({ releases: [ { version: '2013.5.19' }, { version: '2013.6.11' }, @@ -131,6 +138,138 @@ describe('modules/manager/pipenv/artifacts', () => { ]); }); + it('gets python full version from Pipfile', async () => { + GlobalConfig.set({ ...adminConfig, binarySource: 'install' }); + pipFileLock._meta!.requires!.python_full_version = '3.7.6'; + fs.ensureCacheDir.mockResolvedValueOnce(pipenvCacheDir); + fs.ensureCacheDir.mockResolvedValueOnce(virtualenvsCacheDir); + fs.readLocalFile.mockResolvedValueOnce(JSON.stringify(pipFileLock)); + const execSnapshots = mockExecAll(); + fs.readLocalFile.mockResolvedValueOnce(JSON.stringify(pipFileLock)); + + expect( + await updateArtifacts({ + packageFileName: 'Pipfile', + updatedDeps: [], + newPackageFileContent: Fixtures.get('Pipfile1'), + config, + }), + ).toBeNull(); + + expect(execSnapshots).toMatchObject([ + { cmd: 'install-tool python 3.6.2' }, + {}, + { + cmd: 'pipenv lock', + options: { + cwd: '/tmp/github/some/repo', + env: { + PIPENV_CACHE_DIR: pipenvCacheDir, + }, + }, + }, + ]); + }); + + it('gets python version from Pipfile', async () => { + GlobalConfig.set({ ...adminConfig, binarySource: 'install' }); + pipFileLock._meta!.requires!.python_full_version = '3.7.6'; + fs.ensureCacheDir.mockResolvedValueOnce(pipenvCacheDir); + fs.ensureCacheDir.mockResolvedValueOnce(virtualenvsCacheDir); + fs.readLocalFile.mockResolvedValueOnce(JSON.stringify(pipFileLock)); + const execSnapshots = mockExecAll(); + fs.readLocalFile.mockResolvedValueOnce(JSON.stringify(pipFileLock)); + + expect( + await updateArtifacts({ + packageFileName: 'Pipfile', + updatedDeps: [], + newPackageFileContent: Fixtures.get('Pipfile2'), + config, + }), + ).toBeNull(); + + expect(execSnapshots).toMatchObject([ + { cmd: 'install-tool python 3.6.5' }, + {}, + { + cmd: 'pipenv lock', + options: { + cwd: '/tmp/github/some/repo', + env: { + PIPENV_CACHE_DIR: pipenvCacheDir, + }, + }, + }, + ]); + }); + + it('gets full python version from .python-version', async () => { + GlobalConfig.set({ ...adminConfig, binarySource: 'install' }); + fs.ensureCacheDir.mockResolvedValueOnce(pipenvCacheDir); + fs.ensureCacheDir.mockResolvedValueOnce(virtualenvsCacheDir); + fs.readLocalFile.mockResolvedValueOnce(JSON.stringify(pipFileLock)); + const execSnapshots = mockExecAll(); + fs.getSiblingFileName.mockResolvedValueOnce('.python-version' as never); + fs.readLocalFile.mockResolvedValueOnce('3.7.6'); + + expect( + await updateArtifacts({ + packageFileName: 'Pipfile', + updatedDeps: [], + newPackageFileContent: 'some toml', + config, + }), + ).toBeNull(); + + expect(execSnapshots).toMatchObject([ + { cmd: 'install-tool python 3.7.6' }, + {}, + { + cmd: 'pipenv lock', + options: { + cwd: '/tmp/github/some/repo', + env: { + PIPENV_CACHE_DIR: pipenvCacheDir, + }, + }, + }, + ]); + }); + + it('gets python stream, from .python-version', async () => { + GlobalConfig.set({ ...adminConfig, binarySource: 'install' }); + fs.ensureCacheDir.mockResolvedValueOnce(pipenvCacheDir); + fs.ensureCacheDir.mockResolvedValueOnce(virtualenvsCacheDir); + fs.readLocalFile.mockResolvedValueOnce(JSON.stringify(pipFileLock)); + const execSnapshots = mockExecAll(); + fs.getSiblingFileName.mockResolvedValueOnce('.python-version' as never); + fs.readLocalFile.mockResolvedValueOnce('3.8'); + + expect( + await updateArtifacts({ + packageFileName: 'Pipfile', + updatedDeps: [], + newPackageFileContent: 'some toml', + config, + }), + ).toBeNull(); + + expect(execSnapshots).toMatchObject([ + { cmd: 'install-tool python 3.8.5' }, + {}, + { + cmd: 'pipenv lock', + options: { + cwd: '/tmp/github/some/repo', + env: { + PIPENV_CACHE_DIR: pipenvCacheDir, + }, + }, + }, + ]); + }); + it('handles no constraint', async () => { fs.ensureCacheDir.mockResolvedValueOnce(pipenvCacheDir); fs.ensureCacheDir.mockResolvedValueOnce(pipCacheDir); @@ -263,7 +402,7 @@ describe('modules/manager/pipenv/artifacts', () => { it('supports install mode', async () => { GlobalConfig.set({ ...adminConfig, binarySource: 'install' }); - pipFileLock._meta!.requires!.python_full_version = '3.7.6'; + pipFileLock._meta!.requires!.python_version = '3.6'; fs.ensureCacheDir.mockResolvedValueOnce(pipenvCacheDir); fs.ensureCacheDir.mockResolvedValueOnce(pipCacheDir); fs.ensureCacheDir.mockResolvedValueOnce(virtualenvsCacheDir); @@ -290,7 +429,51 @@ describe('modules/manager/pipenv/artifacts', () => { ).not.toBeNull(); expect(execSnapshots).toMatchObject([ - { cmd: 'install-tool python 3.7.6' }, + { cmd: 'install-tool python 3.6.5' }, + { cmd: 'install-tool pipenv 2013.6.12' }, + { + cmd: 'pipenv lock', + options: { + cwd: '/tmp/github/some/repo', + env: { + PIPENV_CACHE_DIR: pipenvCacheDir, + PIP_CACHE_DIR: pipCacheDir, + WORKON_HOME: virtualenvsCacheDir, + }, + }, + }, + ]); + }); + + it('defaults to latest if no lock constraints', async () => { + GlobalConfig.set({ ...adminConfig, binarySource: 'install' }); + fs.ensureCacheDir.mockResolvedValueOnce(pipenvCacheDir); + fs.ensureCacheDir.mockResolvedValueOnce(pipCacheDir); + fs.ensureCacheDir.mockResolvedValueOnce(virtualenvsCacheDir); + fs.readLocalFile.mockResolvedValueOnce(JSON.stringify(pipFileLock)); + // pipenv + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [{ version: '2023.1.2' }], + }); + const execSnapshots = mockExecAll(); + git.getRepoStatus.mockResolvedValue( + partial({ + modified: ['Pipfile.lock'], + }), + ); + fs.readLocalFile.mockResolvedValueOnce('new lock'); + + expect( + await updateArtifacts({ + packageFileName: 'Pipfile', + updatedDeps: [], + newPackageFileContent: 'some new content', + config, + }), + ).not.toBeNull(); + + expect(execSnapshots).toMatchObject([ + { cmd: 'install-tool python 3.10.2' }, { cmd: 'install-tool pipenv 2013.6.12' }, { cmd: 'pipenv lock', @@ -583,7 +766,31 @@ describe('modules/manager/pipenv/artifacts', () => { ]); }); - it('does not pass private credential environment vars if variable names differ from allowed', async () => { + it('returns no host rule on invalid url', () => { + expect(getMatchingHostRule('')).toBeNull(); + }); + + it.each` + credential | result + ${'$USERNAME'} | ${'USERNAME'} + ${'$'} | ${null} + ${''} | ${null} + ${'${USERNAME}'} | ${'USERNAME'} + ${'${USERNAME:-default}'} | ${'USERNAME'} + ${'${COMPLEX_NAME_1:-default}'} | ${'COMPLEX_NAME_1'} + `('extractEnvironmentVariableName(%p)', ({ credential, result }) => { + expect(extractEnvironmentVariableName(credential)).toEqual(result); + }); + + it('warns about duplicate placeholders with different values', () => { + const extraEnv: Opt = { + FOO: '1', + }; + addExtraEnvVariable(extraEnv, 'FOO', '2'); + expect(logger.warn).toHaveBeenCalledOnce(); + }); + + it('updates extraEnv if variable names differ from default', async () => { fs.ensureCacheDir.mockResolvedValueOnce(pipenvCacheDir); fs.ensureCacheDir.mockResolvedValueOnce(pipCacheDir); fs.ensureCacheDir.mockResolvedValueOnce(virtualenvsCacheDir); @@ -596,6 +803,11 @@ describe('modules/manager/pipenv/artifacts', () => { ); fs.readLocalFile.mockResolvedValueOnce('New Pipfile.lock'); + find.mockReturnValueOnce({ + username: 'usernameOne', + password: 'passwordTwo', + }); + expect( await updateArtifacts({ packageFileName: 'Pipfile', @@ -621,6 +833,8 @@ describe('modules/manager/pipenv/artifacts', () => { env: { PIPENV_CACHE_DIR: pipenvCacheDir, WORKON_HOME: virtualenvsCacheDir, + USERNAME_FOO: 'usernameOne', + PAZZWORD: 'passwordTwo', }, }, }, diff --git a/lib/modules/manager/pipenv/artifacts.ts b/lib/modules/manager/pipenv/artifacts.ts index 3b33eb2987daa8..6404c2b9c0a827 100644 --- a/lib/modules/manager/pipenv/artifacts.ts +++ b/lib/modules/manager/pipenv/artifacts.ts @@ -1,3 +1,5 @@ +import is from '@sindresorhus/is'; +import semver from 'semver'; import { TEMPORARY_ERROR } from '../../../constants/error-messages'; import { logger } from '../../../logger'; import type { HostRule } from '../../../types'; @@ -6,12 +8,17 @@ import type { ExecOptions, ExtraEnv, Opt } from '../../../util/exec/types'; import { deleteLocalFile, ensureCacheDir, + getSiblingFileName, readLocalFile, writeLocalFile, } from '../../../util/fs'; import { getRepoStatus } from '../../../util/git'; import { find } from '../../../util/host-rules'; +import { regEx } from '../../../util/regex'; +import { parse as parseToml } from '../../../util/toml'; +import { parseUrl } from '../../../util/url'; import { PypiDatasource } from '../../datasource/pypi'; +import pep440 from '../../versioning/pep440'; import type { UpdateArtifact, UpdateArtifactsConfig, @@ -20,35 +27,91 @@ import type { import { extractPackageFile } from './extract'; import { PipfileLockSchema } from './schema'; -export function getPythonConstraint( +export async function getPythonConstraint( + pipfileName: string, + pipfileContent: string, existingLockFileContent: string, config: UpdateArtifactsConfig, -): string | undefined { +): Promise { const { constraints = {} } = config; const { python } = constraints; if (python) { - logger.debug('Using python constraint from config'); + logger.debug(`Using python constraint ${python} from config`); return python; } + + // Try Pipfile first because it may have had its Python version updated + try { + const pipfile = parseToml(pipfileContent) as any; + const pythonFullVersion = pipfile.requires.python_full_version; + if (pythonFullVersion) { + logger.debug( + `Using python full version ${pythonFullVersion} from Pipfile`, + ); + return `== ${pythonFullVersion}`; + } + const pythonVersion = pipfile.requires.python_version; + if (pythonVersion) { + logger.debug(`Using python version ${pythonVersion} from Pipfile`); + return `== ${pythonVersion}.*`; + } + } catch (err) { + logger.warn({ err }, 'Error parsing Pipfile'); + } + + // Try Pipfile.lock next try { const result = PipfileLockSchema.safeParse(existingLockFileContent); // istanbul ignore if: not easily testable if (!result.success) { - logger.warn({ error: result.error }, 'Invalid Pipfile.lock'); + logger.warn({ err: result.error }, 'Invalid Pipfile.lock'); return undefined; } - if (result.data._meta?.requires?.python_version) { - const pythonVersion = result.data._meta.requires.python_version; + // Exact python version has been included since 2022.10.9. It is more specific than the major.minor version + // https://github.com/pypa/pipenv/blob/main/CHANGELOG.md#2022109-2022-10-09 + const pythonFullVersion = result.data._meta?.requires?.python_full_version; + if (pythonFullVersion) { + logger.debug( + `Using python full version ${pythonFullVersion} from Pipfile.lock`, + ); + return `== ${pythonFullVersion}`; + } + // Before 2022.10.9, only the major.minor version was included + const pythonVersion = result.data._meta?.requires?.python_version; + if (pythonVersion) { + logger.debug(`Using python version ${pythonVersion} from Pipfile.lock`); return `== ${pythonVersion}.*`; } - if (result.data._meta?.requires?.python_full_version) { - const pythonFullVersion = result.data._meta.requires.python_full_version; - return `== ${pythonFullVersion}`; + } catch (err) { + // Do nothing + } + + // Try looking for the contents of .python-version + const pythonVersionFileName = getSiblingFileName( + pipfileName, + '.python-version', + ); + try { + const pythonVersion = await readLocalFile(pythonVersionFileName, 'utf8'); + let pythonVersionConstraint; + if (pythonVersion && pep440.isVersion(pythonVersion)) { + if (pythonVersion.split('.').length >= 3) { + pythonVersionConstraint = `== ${pythonVersion}`; + } else { + pythonVersionConstraint = `== ${pythonVersion}.*`; + } + } + if (pythonVersionConstraint) { + logger.debug( + `Using python version ${pythonVersionConstraint} from ${pythonVersionFileName}`, + ); + return pythonVersionConstraint; } } catch (err) { // Do nothing } + return undefined; } @@ -76,41 +139,135 @@ export function getPipenvConstraint( if (result.data.develop?.pipenv?.version) { return result.data.develop.pipenv.version; } + // Exact python version has been included since 2022.10.9 + const pythonFullVersion = result.data._meta?.requires?.python_full_version; + if (is.string(pythonFullVersion) && semver.valid(pythonFullVersion)) { + // python_full_version was added after 3.6 was already deprecated, so it should be impossible to have a 3.6 version + // https://github.com/pypa/pipenv/blob/main/CHANGELOG.md#2022109-2022-10-09 + if (semver.satisfies(pythonFullVersion, '3.7.*')) { + // Python 3.7 support was dropped in pipenv 2023.10.20 + // https://github.com/pypa/pipenv/blob/main/CHANGELOG.md#20231020-2023-10-20 + return '< 2023.10.20'; + } + // Future deprecations will go here + } + // Before 2022.10.9, only the major.minor version was included + const pythonVersion = result.data._meta?.requires?.python_version; + if (pythonVersion) { + if (pythonVersion === '3.6') { + // Python 3.6 was deprecated in 2022.4.20 + // https://github.com/pypa/pipenv/blob/main/CHANGELOG.md#2022420-2022-04-20 + return '< 2022.4.20'; + } + if (pythonVersion === '3.7') { + // Python 3.7 was deprecated in 2023.10.20 but we shouldn't reach here unless we are < 2022.10.9 + // https://github.com/pypa/pipenv/blob/main/CHANGELOG.md#20231020-2023-10-20 + return '< 2022.10.9'; + } + } } catch (err) { // Do nothing } return ''; } -function getMatchingHostRule(url: string): HostRule { - return find({ hostType: PypiDatasource.id, url }); +export function getMatchingHostRule(url: string): HostRule | null { + const parsedUrl = parseUrl(url); + if (parsedUrl) { + parsedUrl.username = ''; + parsedUrl.password = ''; + const urlWithoutCredentials = parsedUrl.toString(); + + return find({ hostType: PypiDatasource.id, url: urlWithoutCredentials }); + } + return null; } -async function findPipfileSourceUrlWithCredentials( +async function findPipfileSourceUrlsWithCredentials( pipfileContent: string, pipfileName: string, -): Promise { +): Promise { const pipfile = await extractPackageFile(pipfileContent, pipfileName); - if (!pipfile) { - logger.debug('Error parsing Pipfile'); - return null; - } - const credentialTokens = [ - '$USERNAME:', - // eslint-disable-next-line no-template-curly-in-string - '${USERNAME}', - '$PASSWORD@', - // eslint-disable-next-line no-template-curly-in-string - '${PASSWORD}', - ]; - - const sourceWithCredentials = pipfile.registryUrls?.find((url) => - credentialTokens.some((token) => url.includes(token)), + return ( + pipfile?.registryUrls + ?.map(parseUrl) + .filter(is.urlInstance) + .filter((url) => is.nonEmptyStringAndNotWhitespace(url.username)) ?? [] ); +} - // Only one source is currently supported - return sourceWithCredentials ?? null; +/** + * This will extract the actual variable name from an environment-placeholder: + * ${USERNAME:-defaultvalue} will yield 'USERNAME' + */ +export function extractEnvironmentVariableName( + credential: string, +): string | null { + const match = regEx('([a-z0-9_]+)', 'i').exec(decodeURI(credential)); + return match?.length ? match[0] : null; +} + +export function addExtraEnvVariable( + extraEnv: ExtraEnv, + environmentVariableName: string, + environmentValue: string, +): void { + logger.trace( + `Adding ${environmentVariableName} environment variable for pipenv`, + ); + if ( + extraEnv[environmentVariableName] && + extraEnv[environmentVariableName] !== environmentValue + ) { + logger.warn( + `Possible misconfiguration, ${environmentVariableName} is already set to a different value`, + ); + } + extraEnv[environmentVariableName] = environmentValue; +} + +/** + * Pipenv allows configuring source-urls for remote repositories with placeholders for credentials, i.e. http://$USER:$PASS@myprivate.repo + * if a matching host rule exists for that repository, we need to set the corresponding variables. + * Simply substituting them in the URL is not an option as it would impact the hash for the resulting Pipfile.lock + * + */ +async function addCredentialsForSourceUrls( + newPipfileContent: string, + pipfileName: string, + extraEnv: ExtraEnv, +): Promise { + const sourceUrls = await findPipfileSourceUrlsWithCredentials( + newPipfileContent, + pipfileName, + ); + for (const parsedSourceUrl of sourceUrls) { + logger.trace(`Trying to add credentials for ${parsedSourceUrl.toString()}`); + const matchingHostRule = getMatchingHostRule(parsedSourceUrl.toString()); + if (matchingHostRule) { + const usernameVariableName = extractEnvironmentVariableName( + parsedSourceUrl.username, + ); + if (matchingHostRule.username && usernameVariableName) { + addExtraEnvVariable( + extraEnv, + usernameVariableName, + matchingHostRule.username, + ); + } + const passwordVariableName = extractEnvironmentVariableName( + parsedSourceUrl.password, + ); + if (matchingHostRule.password && passwordVariableName) { + addExtraEnvVariable( + extraEnv, + passwordVariableName, + matchingHostRule.password, + ); + } + } + } } export async function updateArtifacts({ @@ -132,7 +289,12 @@ export async function updateArtifacts({ await deleteLocalFile(lockFileName); } const cmd = 'pipenv lock'; - const tagConstraint = getPythonConstraint(existingLockFileContent, config); + const tagConstraint = await getPythonConstraint( + pipfileName, + newPipfileContent, + existingLockFileContent, + config, + ); const pipenvConstraint = getPipenvConstraint( existingLockFileContent, config, @@ -157,26 +319,7 @@ export async function updateArtifacts({ }, ], }; - - const sourceUrl = await findPipfileSourceUrlWithCredentials( - newPipfileContent, - pipfileName, - ); - if (sourceUrl) { - logger.debug({ sourceUrl }, 'Pipfile contains credentials'); - const hostRule = getMatchingHostRule(sourceUrl); - if (hostRule) { - logger.debug('Found matching hostRule for Pipfile credentials'); - if (hostRule.username) { - logger.debug('Adding USERNAME environment variable for pipenv'); - extraEnv.USERNAME = hostRule.username; - } - if (hostRule.password) { - logger.debug('Adding PASSWORD environment variable for pipenv'); - extraEnv.PASSWORD = hostRule.password; - } - } - } + await addCredentialsForSourceUrls(newPipfileContent, pipfileName, extraEnv); execOptions.extraEnv = extraEnv; logger.trace({ cmd }, 'pipenv lock command'); diff --git a/lib/modules/manager/pipenv/extract.spec.ts b/lib/modules/manager/pipenv/extract.spec.ts index fc90ecc04283f3..2f36c980927bb4 100644 --- a/lib/modules/manager/pipenv/extract.spec.ts +++ b/lib/modules/manager/pipenv/extract.spec.ts @@ -33,6 +33,20 @@ describe('modules/manager/pipenv/extract', () => { currentVersion: '0.3.1', datasource: 'pypi', }, + { + depType: 'packages', + depName: 'splinter', + currentValue: '==3.43.1', + currentVersion: '3.43.1', + datasource: 'pypi', + }, + { + depType: 'packages', + depName: 'requests', + currentValue: '==1.1.1', + currentVersion: '1.1.1', + datasource: 'pypi', + }, { depType: 'packages', depName: 'some-other-package', @@ -62,6 +76,20 @@ describe('modules/manager/pipenv/extract', () => { nestedVersion: true, }, }, + { + currentValue: '==1.2.3', + currentVersion: '1.2.3', + datasource: 'pypi', + depName: 'container-specific-package', + depType: 'container', + }, + { + currentValue: '==2.3.4', + currentVersion: '2.3.4', + datasource: 'pypi', + depName: 'function-specific-package', + depType: 'function', + }, { depType: 'dev-packages', depName: 'dev-package', @@ -80,7 +108,7 @@ describe('modules/manager/pipenv/extract', () => { ], }); - expect(res?.deps.filter((dep) => !dep.skipReason)).toHaveLength(4); + expect(res?.deps.filter((dep) => !dep.skipReason)).toHaveLength(8); }); it('marks packages with "extras" as skipReason === unspecified-version', async () => { diff --git a/lib/modules/manager/pipenv/extract.ts b/lib/modules/manager/pipenv/extract.ts index fb0cf43d6ae268..10cc367e370f41 100644 --- a/lib/modules/manager/pipenv/extract.ts +++ b/lib/modules/manager/pipenv/extract.ts @@ -7,10 +7,13 @@ import { regEx } from '../../../util/regex'; import { parse as parseToml } from '../../../util/toml'; import { PypiDatasource } from '../../datasource/pypi'; import type { PackageDependency, PackageFileContent } from '../types'; -import type { PipFile } from './types'; +import type { PipFile, PipRequirement, PipSource } from './types'; // based on https://www.python.org/dev/peps/pep-0508/#names -const packageRegex = regEx(/^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$/i); +export const packagePattern = '[A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9]'; +export const extrasPattern = '(?:\\s*\\[[^\\]]+\\])*'; +const packageRegex = regEx(`^(${packagePattern})(${extrasPattern})$`, 'i'); + const rangePattern: string = RANGE_PATTERN; const specifierPartPattern = `\\s*${rangePattern.replace( @@ -20,31 +23,31 @@ const specifierPartPattern = `\\s*${rangePattern.replace( const specifierPattern = `${specifierPartPattern}(?:,${specifierPartPattern})*`; const specifierRegex = regEx(`^${specifierPattern}$`); function extractFromSection( - pipfile: PipFile, - section: 'packages' | 'dev-packages', + sectionName: string, + pipfileSection: Record, + sources?: PipSource[], ): PackageDependency[] { - const pipfileSection = pipfile[section]; - if (!pipfileSection) { - return []; - } - const deps = Object.entries(pipfileSection) .map((x) => { - const [depName, requirements] = x; + const [packageNameString, requirements] = x; + let depName = packageNameString; + let currentValue: string | undefined; let nestedVersion = false; let skipReason: SkipReason | undefined; - if (requirements.git) { - skipReason = 'git-dependency'; - } else if (requirements.file) { - skipReason = 'file-dependency'; - } else if (requirements.path) { - skipReason = 'local-dependency'; - } else if (requirements.version) { - currentValue = requirements.version; - nestedVersion = true; - } else if (is.object(requirements)) { - skipReason = 'unspecified-version'; + if (is.object(requirements)) { + if (requirements.git) { + skipReason = 'git-dependency'; + } else if (requirements.file) { + skipReason = 'file-dependency'; + } else if (requirements.path) { + skipReason = 'local-dependency'; + } else if (requirements.version) { + currentValue = requirements.version; + nestedVersion = true; + } else { + skipReason = 'unspecified-version'; + } } else { currentValue = requirements; } @@ -52,10 +55,12 @@ function extractFromSection( skipReason = 'unspecified-version'; } if (!skipReason) { - const packageMatches = packageRegex.exec(depName); - if (!packageMatches) { + const packageMatches = packageRegex.exec(packageNameString); + if (packageMatches) { + depName = packageMatches[1]; + } else { logger.debug( - `Skipping dependency with malformed package name "${depName}".`, + `Skipping dependency with malformed package name "${packageNameString}".`, ); skipReason = 'invalid-name'; } @@ -69,7 +74,7 @@ function extractFromSection( } } const dep: PackageDependency = { - depType: section, + depType: sectionName, depName, managerData: {}, }; @@ -88,14 +93,10 @@ function extractFromSection( // TODO #22198 dep.managerData!.nestedVersion = nestedVersion; } - if (requirements.index) { - if (is.array(pipfile.source)) { - const source = pipfile.source.find( - (item) => item.name === requirements.index, - ); - if (source) { - dep.registryUrls = [source.url]; - } + if (sources && is.object(requirements) && requirements.index) { + const source = sources.find((item) => item.name === requirements.index); + if (source) { + dep.registryUrls = [source.url]; } } return dep; @@ -104,6 +105,19 @@ function extractFromSection( return deps; } +function isPipRequirements( + section?: + | Record + | Record + | PipSource[], +): section is Record { + return ( + !is.array(section) && + is.object(section) && + !Object.values(section).some((dep) => !is.object(dep) && !is.string(dep)) + ); +} + export async function extractPackageFile( content: string, packageFile: string, @@ -119,14 +133,33 @@ export async function extractPackageFile( return null; } const res: PackageFileContent = { deps: [] }; - if (pipfile.source) { - res.registryUrls = pipfile.source.map((source) => source.url); + + const sources = pipfile?.source; + + if (sources) { + res.registryUrls = sources.map((source) => source.url); } - res.deps = [ - ...extractFromSection(pipfile, 'packages'), - ...extractFromSection(pipfile, 'dev-packages'), - ]; + let pipenv_constraint: PipRequirement | undefined; + + res.deps = Object.entries(pipfile) + .map(([category, section]) => { + if ( + category === 'source' || + category === 'requires' || + !isPipRequirements(section) + ) { + return []; + } + + if (section.pipenv && !pipenv_constraint) { + pipenv_constraint = section.pipenv; + } + + return extractFromSection(category, section, sources); + }) + .flat(); + if (!res.deps.length) { return null; } @@ -139,10 +172,8 @@ export async function extractPackageFile( extractedConstraints.python = `== ${pipfile.requires.python_full_version}`; } - if (is.nonEmptyString(pipfile.packages?.pipenv)) { - extractedConstraints.pipenv = pipfile.packages.pipenv; - } else if (is.nonEmptyString(pipfile['dev-packages']?.pipenv)) { - extractedConstraints.pipenv = pipfile['dev-packages'].pipenv; + if (pipenv_constraint) { + extractedConstraints.pipenv = pipenv_constraint; } const lockFileName = `${packageFile}.lock`; diff --git a/lib/modules/manager/pipenv/readme.md b/lib/modules/manager/pipenv/readme.md index 3bb87a4c992b13..1f93b6f8db4a31 100644 --- a/lib/modules/manager/pipenv/readme.md +++ b/lib/modules/manager/pipenv/readme.md @@ -1,6 +1,8 @@ `Pipenv.lock` updating is supported. -The following `depTypes` are supported by the Pipenv manager: +The Pipenv manager supports the default `depTypes`: - `packages` - `dev-packages` + +and also extracts dependencies from other package category groups, using the group name as the `depType`. diff --git a/lib/modules/manager/pipenv/types.ts b/lib/modules/manager/pipenv/types.ts index b3e914aea1c05f..51cbb9c186ca0e 100644 --- a/lib/modules/manager/pipenv/types.ts +++ b/lib/modules/manager/pipenv/types.ts @@ -8,13 +8,25 @@ export interface PipFile { packages?: Record; 'dev-packages'?: Record; + requires?: Record; -} -export interface PipRequirement { - index?: string; - version?: string; - path?: string; - file?: string; - git?: string; + /* Elements not defined above are always PipRequirement records + * however Typescript requires us to enumerate all possible types on all possible keys. + */ + [index: string]: + | undefined + | PipSource[] + | Record + | Record; } + +export type PipRequirement = + | string + | { + index?: string; + version?: string; + path?: string; + file?: string; + git?: string; + }; diff --git a/lib/modules/manager/poetry/artifacts.ts b/lib/modules/manager/poetry/artifacts.ts index f45bbdcb867812..d5582f619c5910 100644 --- a/lib/modules/manager/poetry/artifacts.ts +++ b/lib/modules/manager/poetry/artifacts.ts @@ -60,8 +60,11 @@ export function getPoetryRequirement( const firstLine = existingLockFileContent.split('\n')[0]; const poetryVersionMatch = firstLine.match(/by Poetry ([\d\\.]+)/); if (poetryVersionMatch?.[1]) { - logger.debug('Using poetry version from poetry.lock header'); - return poetryVersionMatch[1]; + const poetryVersion = poetryVersionMatch[1]; + logger.debug( + `Using poetry version ${poetryVersion} from poetry.lock header`, + ); + return poetryVersion; } const { val: lockfilePoetryConstraint } = Result.parse( @@ -69,7 +72,9 @@ export function getPoetryRequirement( Lockfile.transform(({ poetryConstraint }) => poetryConstraint), ).unwrap(); if (lockfilePoetryConstraint) { - logger.debug('Using poetry version from poetry.lock metadata'); + logger.debug( + `Using poetry version ${lockfilePoetryConstraint} from poetry.lock metadata`, + ); return lockfilePoetryConstraint; } @@ -78,7 +83,9 @@ export function getPoetryRequirement( PoetrySchemaToml.transform(({ poetryRequirement }) => poetryRequirement), ).unwrap(); if (pyprojectPoetryConstraint) { - logger.debug('Using poetry version from pyproject.toml'); + logger.debug( + `Using poetry version ${pyprojectPoetryConstraint} from pyproject.toml`, + ); return pyprojectPoetryConstraint; } diff --git a/lib/modules/manager/poetry/extract.spec.ts b/lib/modules/manager/poetry/extract.spec.ts index 62f43868f7287a..240511cf44351b 100644 --- a/lib/modules/manager/poetry/extract.spec.ts +++ b/lib/modules/manager/poetry/extract.spec.ts @@ -171,7 +171,7 @@ describe('modules/manager/poetry/extract', () => { }); }); - it('parses git dependencies long commit hashs on http urls', async () => { + it('parses git dependencies long commit hashes on http urls', async () => { const content = codeBlock` [tool.poetry.dependencies] fastapi = {git = "https://github.com/tiangolo/fastapi.git", rev="6f5aa81c076d22e38afbe7d602db6730e28bc3cc"} @@ -196,7 +196,7 @@ describe('modules/manager/poetry/extract', () => { ]); }); - it('parses git dependencies short commit hashs on http urls', async () => { + it('parses git dependencies short commit hashes on http urls', async () => { const content = codeBlock` [tool.poetry.dependencies] fastapi = {git = "https://github.com/tiangolo/fastapi.git", rev="6f5aa81"} @@ -221,7 +221,7 @@ describe('modules/manager/poetry/extract', () => { ]); }); - it('parses git dependencies long commit hashs on ssh urls', async () => { + it('parses git dependencies long commit hashes on ssh urls', async () => { const content = codeBlock` [tool.poetry.dependencies] fastapi = {git = "git@github.com:tiangolo/fastapi.git", rev="6f5aa81c076d22e38afbe7d602db6730e28bc3cc"} @@ -246,7 +246,7 @@ describe('modules/manager/poetry/extract', () => { ]); }); - it('parses git dependencies long commit hashs on http urls with branch marker', async () => { + it('parses git dependencies long commit hashes on http urls with branch marker', async () => { const content = codeBlock` [tool.poetry.dependencies] fastapi = {git = "https://github.com/tiangolo/fastapi.git", branch="develop", rev="6f5aa81c076d22e38afbe7d602db6730e28bc3cc"} @@ -302,6 +302,29 @@ describe('modules/manager/poetry/extract', () => { expect(res).toHaveLength(2); }); + it('parses git dependencies with tags that are not on GitHub', async () => { + const content = codeBlock` + [tool.poetry.dependencies] + aws-sam = {git = "https://gitlab.com/gitlab-examples/aws-sam.git", tag="1.2.3"} + platform-tools = {git = "https://some.company.com/platform-tools", tag="1.2.3"} + `; + const res = await extractPackageFile(content, filename); + expect(res?.deps).toMatchObject([ + { + datasource: 'gitlab-tags', + depName: 'aws-sam', + packageName: 'gitlab-examples/aws-sam', + currentValue: '1.2.3', + }, + { + datasource: 'git-tags', + depName: 'platform-tools', + packageName: 'https://some.company.com/platform-tools', + currentValue: '1.2.3', + }, + ]); + }); + it('skips git dependencies', async () => { const content = codeBlock` [tool.poetry.dependencies] @@ -327,18 +350,6 @@ describe('modules/manager/poetry/extract', () => { expect(res).toHaveLength(2); }); - it('skips git dependencies on tags that are not in github', async () => { - const content = codeBlock` - [tool.poetry.dependencies] - aws-sam = {git = "https://gitlab.com/gitlab-examples/aws-sam.git", tag="1.2.3"} - `; - const res = (await extractPackageFile(content, filename))!.deps; - expect(res[0].depName).toBe('aws-sam'); - expect(res[0].currentValue).toBe('1.2.3'); - expect(res[0].skipReason).toBe('git-dependency'); - expect(res).toHaveLength(1); - }); - it('skips path dependencies', async () => { const content = codeBlock` [tool.poetry.dependencies] diff --git a/lib/modules/manager/poetry/index.ts b/lib/modules/manager/poetry/index.ts index 82a131c8e26ea9..f8a52fb9467949 100644 --- a/lib/modules/manager/poetry/index.ts +++ b/lib/modules/manager/poetry/index.ts @@ -1,7 +1,9 @@ import type { Category } from '../../../constants'; import { GitRefsDatasource } from '../../datasource/git-refs'; +import { GitTagsDatasource } from '../../datasource/git-tags'; import { GithubReleasesDatasource } from '../../datasource/github-releases'; import { GithubTagsDatasource } from '../../datasource/github-tags'; +import { GitlabTagsDatasource } from '../../datasource/gitlab-tags'; import { PypiDatasource } from '../../datasource/pypi'; export { bumpPackageVersion } from '../pep621/update'; @@ -13,7 +15,9 @@ export const supportedDatasources = [ PypiDatasource.id, GithubTagsDatasource.id, GithubReleasesDatasource.id, + GitlabTagsDatasource.id, GitRefsDatasource.id, + GitTagsDatasource.id, ]; export const supportsLockFileMaintenance = true; diff --git a/lib/modules/manager/poetry/schema.ts b/lib/modules/manager/poetry/schema.ts index 621f844b7c5a48..9617894a2f3ee3 100644 --- a/lib/modules/manager/poetry/schema.ts +++ b/lib/modules/manager/poetry/schema.ts @@ -5,8 +5,11 @@ import { regEx } from '../../../util/regex'; import { LooseArray, LooseRecord, Toml } from '../../../util/schema-utils'; import { uniq } from '../../../util/uniq'; import { GitRefsDatasource } from '../../datasource/git-refs'; +import { GitTagsDatasource } from '../../datasource/git-tags'; import { GithubTagsDatasource } from '../../datasource/github-tags'; +import { GitlabTagsDatasource } from '../../datasource/gitlab-tags'; import { PypiDatasource } from '../../datasource/pypi'; +import { normalizePythonDepName } from '../../datasource/pypi/common'; import * as gitVersioning from '../../versioning/git'; import * as pep440Versioning from '../../versioning/pep440'; import * as poetryVersioning from '../../versioning/poetry'; @@ -42,19 +45,24 @@ const PoetryGitDependency = z .transform(({ git, tag, version, branch, rev }): PackageDependency => { if (tag) { const { source, owner, name } = parseGitUrl(git); + const repo = `${owner}/${name}`; if (source === 'github.com') { - const repo = `${owner}/${name}`; return { datasource: GithubTagsDatasource.id, currentValue: tag, packageName: repo, }; + } else if (source === 'gitlab.com') { + return { + datasource: GitlabTagsDatasource.id, + currentValue: tag, + packageName: repo, + }; } else { return { - datasource: GitRefsDatasource.id, + datasource: GitTagsDatasource.id, currentValue: tag, packageName: git, - skipReason: 'git-dependency', }; } } @@ -67,14 +75,14 @@ const PoetryGitDependency = z replaceString: rev, packageName: git, }; - } else { - return { - datasource: GitRefsDatasource.id, - currentValue: version, - packageName: git, - skipReason: 'git-dependency', - }; } + + return { + datasource: GitRefsDatasource.id, + currentValue: version, + packageName: git, + skipReason: 'git-dependency', + }; }); const PoetryPypiDependency = z.union([ @@ -150,10 +158,7 @@ export const PoetryDependencies = LooseRecord( for (const [depName, dep] of Object.entries(record)) { dep.depName = depName; if (!dep.packageName) { - const pep503NormalizeRegex = regEx(/[-_.]+/g); - const packageName = depName - .toLowerCase() - .replace(pep503NormalizeRegex, '-'); + const packageName = normalizePythonDepName(depName); if (depName !== packageName) { dep.packageName = packageName; } @@ -364,7 +369,7 @@ export const PoetrySchemaToml = Toml.pipe(PoetrySchema); const poetryConstraint: Record = { '1.0': '<1.1.0', '1.1': '<1.3.0', - '2.0': '>=1.3.0', + '2.0': '>=1.3.0 <1.4.0', // 1.4.0 introduced embedding of the poetry version in lock file header }; export const Lockfile = Toml.pipe( diff --git a/lib/modules/manager/pre-commit/index.ts b/lib/modules/manager/pre-commit/index.ts index 914529ed56e665..d492b8174f822c 100644 --- a/lib/modules/manager/pre-commit/index.ts +++ b/lib/modules/manager/pre-commit/index.ts @@ -11,7 +11,10 @@ export const defaultConfig = { commitMessageTopic: 'pre-commit hook {{depName}}', enabled: false, fileMatch: ['(^|/)\\.pre-commit-config\\.ya?ml$'], - prBodyNotes: [ - 'Note: The `pre-commit` manager in Renovate is not supported by the `pre-commit` maintainers or community. Please do not report any problems there, instead [create a Discussion in the Renovate repository](https://github.com/renovatebot/renovate/discussions/new) if you have any questions.', - ], + prBodyNotes: process.env.RENOVATE_X_SUPPRESS_PRE_COMMIT_WARNING + ? /* istanbul ignore next */ + [] + : [ + 'Note: The `pre-commit` manager in Renovate is not supported by the `pre-commit` maintainers or community. Please do not report any problems there, instead [create a Discussion in the Renovate repository](https://github.com/renovatebot/renovate/discussions/new) if you have any questions.', + ], }; diff --git a/lib/modules/manager/pub/extract.spec.ts b/lib/modules/manager/pub/extract.spec.ts index ce33cd01f376b6..d9897408abea6e 100644 --- a/lib/modules/manager/pub/extract.spec.ts +++ b/lib/modules/manager/pub/extract.spec.ts @@ -42,7 +42,13 @@ describe('modules/manager/pub/extract', () => { dependencies: meta: 'something' foo: 1.0.0 + transmogrify: + hosted: + name: transmogrify + url: https://some-package-server.com + version: ^1.4.0 bar: + hosted: 'some-url' version: 1.1.0 baz: non-sense: true @@ -68,12 +74,20 @@ describe('modules/manager/pub/extract', () => { datasource: dartDatasource, skipReason, }, + { + currentValue: '^1.4.0', + depName: 'transmogrify', + depType: dependenciesDepType, + datasource: dartDatasource, + registryUrls: ['https://some-package-server.com'], + }, { currentValue: '1.1.0', depName: 'bar', depType: dependenciesDepType, datasource: dartDatasource, skipReason, + registryUrls: ['some-url'], }, { currentValue: '', diff --git a/lib/modules/manager/pub/extract.ts b/lib/modules/manager/pub/extract.ts index 87d37223e818ce..f83cbdf320c737 100644 --- a/lib/modules/manager/pub/extract.ts +++ b/lib/modules/manager/pub/extract.ts @@ -31,10 +31,19 @@ function extractFromSection( let currentValue = sectionContent[depName]; let skipReason: SkipReason | undefined; + let registryUrls: string[] | undefined; if (!is.string(currentValue)) { const version = currentValue.version; const path = currentValue.path; + const hosted = currentValue.hosted; + + if (is.string(hosted)) { + registryUrls = [hosted]; + } else if (is.string(hosted?.url)) { + registryUrls = [hosted.url]; + } + if (version) { currentValue = version; } else if (path) { @@ -50,6 +59,7 @@ function extractFromSection( depType: sectionKey, currentValue, datasource: DartDatasource.id, + ...(registryUrls && { registryUrls }), skipReason, }); } diff --git a/lib/modules/manager/pub/schema.ts b/lib/modules/manager/pub/schema.ts index 667cc063b5c3c0..f7c57158dc7e58 100644 --- a/lib/modules/manager/pub/schema.ts +++ b/lib/modules/manager/pub/schema.ts @@ -5,7 +5,14 @@ const PubspecDependencySchema = LooseRecord( z.string(), z.union([ z.string(), - z.object({ version: z.string().optional(), path: z.string().optional() }), + z.object({ + version: z.string().optional(), + path: z.string().optional(), + hosted: z.union([ + z.string().optional(), + z.object({ name: z.string().optional(), url: z.string().optional() }), + ]), + }), ]), ); diff --git a/lib/modules/manager/sbt/extract.spec.ts b/lib/modules/manager/sbt/extract.spec.ts index fe4a05bf149db6..98f919b21f3462 100644 --- a/lib/modules/manager/sbt/extract.spec.ts +++ b/lib/modules/manager/sbt/extract.spec.ts @@ -556,5 +556,31 @@ describe('modules/manager/sbt/extract', () => { const packages = await extractAllPackageFiles({}, ['build.sbt']); expect(packages).toBeEmpty(); }); + + it('extracts build properties correctly', async () => { + const buildProps = codeBlock` + sbt.version=1.6.0 + `; + fs.readLocalFile.mockResolvedValueOnce(buildProps); + const packages = await extractAllPackageFiles({}, [ + 'project/build.properties', + ]); + expect(packages).toMatchObject([ + { + deps: [ + { + datasource: 'github-releases', + packageName: 'sbt/sbt', + depName: 'sbt/sbt', + currentValue: '1.6.0', + replaceString: 'sbt.version=1.6.0', + versioning: 'semver', + extractVersion: '^v(?\\S+)', + registryUrls: [], + }, + ], + }, + ]); + }); }); }); diff --git a/lib/modules/manager/sbt/extract.ts b/lib/modules/manager/sbt/extract.ts index 4933298b48ac92..42b2293af4da6e 100644 --- a/lib/modules/manager/sbt/extract.ts +++ b/lib/modules/manager/sbt/extract.ts @@ -334,6 +334,7 @@ export function extractPackageFile( currentValue: sbtVersion, replaceString: matchString, extractVersion: '^v(?\\S+)', + registryUrls: [], }; return { @@ -394,13 +395,14 @@ export async function extractAllPackageFiles( } for (const pkg of packages) { for (const dep of pkg.deps) { - dep.registryUrls ??= []; - if (proxyUrls.length > 0) { - dep.registryUrls.unshift(...proxyUrls); - } else if (dep.depType === 'plugin') { - dep.registryUrls.unshift(SBT_PLUGINS_REPO, SBT_MVN_REPO); - } else { - dep.registryUrls.unshift(SBT_MVN_REPO); + if (dep.datasource !== GithubReleasesDatasource.id) { + if (proxyUrls.length > 0) { + dep.registryUrls!.unshift(...proxyUrls); + } else if (dep.depType === 'plugin') { + dep.registryUrls!.unshift(SBT_PLUGINS_REPO, SBT_MVN_REPO); + } else { + dep.registryUrls!.unshift(SBT_MVN_REPO); + } } } } diff --git a/lib/modules/manager/sbt/index.ts b/lib/modules/manager/sbt/index.ts index 671f3e729a5b52..d143d5ebf89373 100644 --- a/lib/modules/manager/sbt/index.ts +++ b/lib/modules/manager/sbt/index.ts @@ -5,7 +5,7 @@ import { SbtPackageDatasource } from '../../datasource/sbt-package'; import { SbtPluginDatasource } from '../../datasource/sbt-plugin'; import * as ivyVersioning from '../../versioning/ivy'; -export { extractAllPackageFiles } from './extract'; +export { extractAllPackageFiles, extractPackageFile } from './extract'; export { bumpPackageVersion } from './update'; export const supportedDatasources = [ diff --git a/lib/modules/manager/scalafmt/extract.spec.ts b/lib/modules/manager/scalafmt/extract.spec.ts new file mode 100644 index 00000000000000..f371daa748310d --- /dev/null +++ b/lib/modules/manager/scalafmt/extract.spec.ts @@ -0,0 +1,38 @@ +import { codeBlock } from 'common-tags'; +import { extractPackageFile } from './extract'; + +describe('modules/manager/scalafmt/extract', () => { + describe('extractPackageFile()', () => { + it('extracts version correctly', () => { + const scalafmtConf = codeBlock` + version = 3.8.0 + `; + const packages = extractPackageFile(scalafmtConf); + expect(packages).toMatchObject({ + deps: [ + { + datasource: 'github-releases', + packageName: 'scalameta/scalafmt', + depName: 'scalafmt', + currentValue: '3.8.0', + versioning: 'semver', + extractVersion: '^v(?\\S+)', + }, + ], + }); + }); + + it('ignore file if no version specified', () => { + const scalafmtConf = codeBlock` + maxColumn = 80 + `; + const packages = extractPackageFile(scalafmtConf); + expect(packages).toBeNull(); + }); + + it('should return empty packagefiles is no content is provided', () => { + const packages = extractPackageFile(''); + expect(packages).toBeNull(); + }); + }); +}); diff --git a/lib/modules/manager/scalafmt/extract.ts b/lib/modules/manager/scalafmt/extract.ts new file mode 100644 index 00000000000000..0c484b2dd83310 --- /dev/null +++ b/lib/modules/manager/scalafmt/extract.ts @@ -0,0 +1,30 @@ +import { regEx } from '../../../util/regex'; +import { GithubReleasesDatasource } from '../../datasource/github-releases'; +import * as semverVersioning from '../../versioning/semver'; +import type { PackageDependency, PackageFileContent } from '../types'; + +const scalafmtVersionRegex = regEx( + 'version *= *(?\\d+\\.\\d+\\.\\d+)', +); + +export function extractPackageFile(content: string): PackageFileContent | null { + const regexResult = scalafmtVersionRegex.exec(content); + const scalafmtVersion = regexResult?.groups?.version; + + if (!scalafmtVersion) { + return null; + } + + const scalafmtDependency: PackageDependency = { + datasource: GithubReleasesDatasource.id, + depName: 'scalafmt', + packageName: 'scalameta/scalafmt', + versioning: semverVersioning.id, + currentValue: scalafmtVersion, + extractVersion: '^v(?\\S+)', + }; + + return { + deps: [scalafmtDependency], + }; +} diff --git a/lib/modules/manager/scalafmt/index.ts b/lib/modules/manager/scalafmt/index.ts new file mode 100644 index 00000000000000..25b512b938f3f0 --- /dev/null +++ b/lib/modules/manager/scalafmt/index.ts @@ -0,0 +1,12 @@ +import type { Category } from '../../../constants'; +import { GithubReleasesDatasource } from '../../datasource/github-releases'; + +export { extractPackageFile } from './extract'; + +export const supportedDatasources = [GithubReleasesDatasource.id]; + +export const defaultConfig = { + fileMatch: ['(^|/)\\.scalafmt.conf$'], +}; + +export const categories: Category[] = ['java']; diff --git a/lib/modules/manager/scalafmt/readme.md b/lib/modules/manager/scalafmt/readme.md new file mode 100644 index 00000000000000..517b7966b07b03 --- /dev/null +++ b/lib/modules/manager/scalafmt/readme.md @@ -0,0 +1,3 @@ +Extracts scalafmt version from `.scalafmt.conf` file. + +New versions of Scalafmt are looked up on Github Releases. diff --git a/lib/modules/manager/tekton/readme.md b/lib/modules/manager/tekton/readme.md index ed59c0601936bc..d68eb423d3cf95 100644 --- a/lib/modules/manager/tekton/readme.md +++ b/lib/modules/manager/tekton/readme.md @@ -17,7 +17,7 @@ Read the [Tekton Pipeline remote resolution docs](https://tekton.dev/docs/pipeli ### Using a PipelinesAsCode remote URL reference -By specifying the annotation with a remote task or a remote pipeline based on the recommended way using [git based versioning](https://github.com/tektoncd/community/blob/main/teps/0115-tekton-catalog-git-based-versioning.md). How this can be used can be seen in the example below. +By specifying the annotation with a remote task or a remote pipeline based on the recommended way using [git based versioning](https://github.com/tektoncd/community/blob/main/teps/0115-tekton-catalog-git-based-versioning/index.md). How this can be used can be seen in the example below. ```yaml title="How an annotation could look like in an pipeline-run.yaml" apiVersion: tekton.dev/v1 @@ -78,4 +78,4 @@ As an example, the following config matches all the YAML files in a repository: } ``` -See our [versioning](../../versioning.md) documentation for details on the existing versioning rules and possible alterations. +See our [versioning](../../versioning/index.md) documentation for details on the existing versioning rules and possible alterations. diff --git a/lib/modules/manager/terraform/extractors/resources/helm-release.ts b/lib/modules/manager/terraform/extractors/resources/helm-release.ts index c2f5f8ef6b6aac..835ae6ab2380e9 100644 --- a/lib/modules/manager/terraform/extractors/resources/helm-release.ts +++ b/lib/modules/manager/terraform/extractors/resources/helm-release.ts @@ -3,7 +3,7 @@ import { logger } from '../../../../../logger'; import { joinUrlParts } from '../../../../../util/url'; import { HelmDatasource } from '../../../../datasource/helm'; import { getDep } from '../../../dockerfile/extract'; -import { isOCIRegistry } from '../../../helmv3/utils'; +import { isOCIRegistry, removeOCIPrefix } from '../../../helmv3/oci'; import type { ExtractConfig, PackageDependency } from '../../../types'; import { DependencyExtractor } from '../../base'; import type { TerraformDefinitionFile } from '../../hcl/types'; @@ -50,7 +50,7 @@ export class HelmReleaseExtractor extends DependencyExtractor { dep.skipReason = 'invalid-name'; } else if (isOCIRegistry(helmRelease.chart)) { // For oci charts, we remove the oci:// and use the docker datasource - dep.depName = helmRelease.chart.replace('oci://', ''); + dep.depName = removeOCIPrefix(helmRelease.chart); this.processOCI(dep.depName, config, dep); } else if (checkIfStringIsPath(helmRelease.chart)) { dep.skipReason = 'local-chart'; @@ -59,7 +59,7 @@ export class HelmReleaseExtractor extends DependencyExtractor { // For oci charts, we remove the oci:// and use the docker datasource this.processOCI( joinUrlParts( - helmRelease.repository.replace('oci://', ''), + removeOCIPrefix(helmRelease.repository), helmRelease.chart, ), config, diff --git a/lib/modules/manager/terraform/lockfile/__fixtures__/test_with_folder.zip b/lib/modules/manager/terraform/lockfile/__fixtures__/test_with_folder.zip new file mode 100644 index 00000000000000..3800dc1b1ed1b6 Binary files /dev/null and b/lib/modules/manager/terraform/lockfile/__fixtures__/test_with_folder.zip differ diff --git a/lib/modules/manager/terraform/lockfile/hash.spec.ts b/lib/modules/manager/terraform/lockfile/hash.spec.ts index b0e82c83b79446..b51609c99eaf75 100644 --- a/lib/modules/manager/terraform/lockfile/hash.spec.ts +++ b/lib/modules/manager/terraform/lockfile/hash.spec.ts @@ -1,5 +1,6 @@ import { createReadStream } from 'node:fs'; import { DirectoryResult, dir } from 'tmp-promise'; +import upath from 'upath'; import { Fixtures } from '../../../../../test/fixtures'; import * as httpMock from '../../../../../test/http-mock'; import { getFixturePath, logger } from '../../../../../test/util'; @@ -426,4 +427,17 @@ describe('modules/manager/terraform/lockfile/hash', () => { 'h1:I2F2atKZqKEOYk1tTLe15Llf9rVqxz48ZL1eZB9g8zM=', ]); }); + + describe('hashOfZipContent', () => { + const zipWithFolderPath = Fixtures.getPath('test_with_folder.zip'); + + it('return hash for content with subfolders', async () => { + await expect( + TerraformProviderHash.hashOfZipContent( + zipWithFolderPath, + upath.join(cacheDir.path, 'test'), + ), + ).resolves.toBe('g92f/mR2hlVmeWBlplxxJyP2H3fdyPwYccr7uJhcRz8='); + }); + }); }); diff --git a/lib/modules/manager/terraform/lockfile/hash.ts b/lib/modules/manager/terraform/lockfile/hash.ts index 5736ed33fe4181..478d6f442dfe43 100644 --- a/lib/modules/manager/terraform/lockfile/hash.ts +++ b/lib/modules/manager/terraform/lockfile/hash.ts @@ -12,7 +12,6 @@ import * as fs from '../../../../util/fs'; import { ensureCacheDir } from '../../../../util/fs'; import { Http } from '../../../../util/http'; import * as p from '../../../../util/promises'; -import { regEx } from '../../../../util/regex'; import { TerraformProviderDatasource } from '../../../datasource/terraform-provider'; import type { TerraformBuild } from '../../../datasource/terraform-provider/types'; @@ -23,44 +22,88 @@ export class TerraformProviderHash { static hashCacheTTL = 10080; // in minutes == 1 week - private static async hashFiles(files: string[]): Promise { + private static async hashElementList( + basePath: string, + fileSystemEntries: string[], + ): Promise { const rootHash = crypto.createHash('sha256'); - for (const file of files) { + for (const entryPath of fileSystemEntries) { + const absolutePath = upath.resolve(basePath, entryPath); + if (!(await fs.cachePathIsFile(absolutePath))) { + continue; + } + // build for every file a line looking like "aaaaaaaaaaaaaaa file.txt\n" - const hash = crypto.createHash('sha256'); - // a sha256sum displayed as lowercase hex string to root hash - const fileBuffer = await fs.readCacheFile(file); + // get hash of specific file + const hash = crypto.createHash('sha256'); + const fileBuffer = await fs.readCacheFile(absolutePath); hash.update(fileBuffer); - rootHash.update(hash.digest('hex')); - // add double space, the filename and a new line char - rootHash.update(' '); - const fileName = file.replace(regEx(/^.*[/]/), ''); - rootHash.update(fileName); - rootHash.update('\n'); + const line = `${hash.digest('hex')} ${upath.normalize(entryPath)}\n`; + rootHash.update(line); } return rootHash.digest('base64'); } + /** + * This is a reimplementation of the Go H1 hash algorithm found at https://github.com/golang/mod/blob/master/sumdb/dirhash/hash.go + * The package provides two function HashDir and HashZip where the first is for hashing the contents of a directory + * and the second for doing the same but implicitly extracting the contents first. + * + * The problem starts with that there is a bug which leads to the fact that HashDir and HashZip do not return the same + * hash if there are folders inside the content which should be hashed. + * + * In a folder structure such as + * . + * ├── Readme.md + * └── readme-assets/ + * └── image.jpg + * + * HashDir will create a list of following entries which in turn will hash again + * aaaaaaaaaaa Readme.md\n + * ccccccccccc readme-assets/image.jpg\n + * + * HashZip in contrast will not filter out the directory itself but rather includes it in the hash list + * aaaaaaaaaaa Readme.md\n + * bbbbbbbbbbb readme-assets/\n + * ccccccccccc readme-assets/image.jpg\n + * + * As the resulting string is used to generate the final hash it will differ based on which function has been used. + * The issue is tracked here: https://github.com/golang/go/issues/53448 + * + * This implementation follows the intended implementation and filters out folder entries. + * Terraform seems NOT to use HashZip for provider validation, but rather extracts it and then do the hash calculation + * even as both are set up in their code base. + * https://github.com/hashicorp/terraform/blob/3fdfbd69448b14a4982b3c62a5d36835956fcbaa/internal/getproviders/hash.go#L283-L305 + * + * @param zipFilePath path to the zip file + * @param extractPath path to where to temporarily extract the data + */ static async hashOfZipContent( zipFilePath: string, extractPath: string, ): Promise { - await extract(zipFilePath, { dir: extractPath }); - const files = await fs.listCacheDir(extractPath); - // the h1 hashing algorithms requires that the files are sorted by filename - const sortedFiles = files.sort((a, b) => a.localeCompare(b)); - const filesWithPath = sortedFiles.map((file) => `${extractPath}/${file}`); - - const result = await TerraformProviderHash.hashFiles(filesWithPath); - + await extract(zipFilePath, { + dir: extractPath, + }); + const hash = await this.hashOfDir(extractPath); // delete extracted files await fs.rmCache(extractPath); - return result; + return hash; + } + + static async hashOfDir(dirPath: string): Promise { + const elements = await fs.listCacheDir(dirPath, { recursive: true }); + + const sortedFileSystemObjects = elements.sort(); + return await TerraformProviderHash.hashElementList( + dirPath, + sortedFileSystemObjects, + ); } @cache({ diff --git a/lib/modules/manager/terraform/lockfile/index.spec.ts b/lib/modules/manager/terraform/lockfile/index.spec.ts index 7557011905d8e0..c6df3ccf13cb65 100644 --- a/lib/modules/manager/terraform/lockfile/index.spec.ts +++ b/lib/modules/manager/terraform/lockfile/index.spec.ts @@ -76,7 +76,7 @@ describe('modules/manager/terraform/lockfile/index', () => { it('update single dependency with exact constraint and depType provider', async () => { fs.readLocalFile.mockResolvedValueOnce(codeBlock` - provider "registry.terraform.io/hashicorp/aws" { + provider "registry.opentofu.org/hashicorp/aws" { version = "3.0.0" constraints = "3.0.0" hashes = [ @@ -111,7 +111,7 @@ describe('modules/manager/terraform/lockfile/index', () => { { file: { contents: codeBlock` - provider "registry.terraform.io/hashicorp/aws" { + provider "registry.opentofu.org/hashicorp/aws" { version = "3.36.0" constraints = "3.36.0" hashes = [ @@ -126,7 +126,7 @@ describe('modules/manager/terraform/lockfile/index', () => { }, ]); expect(mockHash.mock.calls).toEqual([ - ['https://registry.terraform.io', 'hashicorp/aws', '3.36.0'], + ['https://registry.opentofu.org', 'hashicorp/aws', '3.36.0'], ]); }); diff --git a/lib/modules/manager/terraform/lockfile/index.ts b/lib/modules/manager/terraform/lockfile/index.ts index aca7419f3d78df..c88cb5c7e1e934 100644 --- a/lib/modules/manager/terraform/lockfile/index.ts +++ b/lib/modules/manager/terraform/lockfile/index.ts @@ -3,7 +3,6 @@ import { logger } from '../../../../logger'; import * as p from '../../../../util/promises'; import { escapeRegExp, regEx } from '../../../../util/regex'; import { GetPkgReleasesConfig, getPkgReleases } from '../../../datasource'; -import { TerraformProviderDatasource } from '../../../datasource/terraform-provider'; import { get as getVersioning } from '../../../versioning'; import type { UpdateArtifact, @@ -167,9 +166,6 @@ export async function updateArtifacts({ massageProviderLookupName(dep); const { registryUrls, newVersion, packageName } = dep; - const registryUrl = registryUrls - ? registryUrls[0] - : TerraformProviderDatasource.defaultRegistryUrls[0]; const updateLock = locks.find( (value) => value.packageName === packageName, ); @@ -191,6 +187,10 @@ export async function updateArtifacts({ continue; } } + + // use registryURL defined in the update and fall back to the one defined in the lockfile + const registryUrl = registryUrls?.[0] ?? updateLock.registryUrl; + const newConstraint = getNewConstraint(dep, updateLock.constraints); const update: ProviderLockUpdate = { // TODO #22198 diff --git a/lib/modules/manager/terraform/readme.md b/lib/modules/manager/terraform/readme.md index 1882674192f076..673634d7e9e71b 100644 --- a/lib/modules/manager/terraform/readme.md +++ b/lib/modules/manager/terraform/readme.md @@ -1,3 +1,21 @@ +### Terraform vs OpenTofu + +Renovate can not know if you want to use the Terraform or OpenTofu registry. +By default, Renovate uses the Terraform registry (`registry.terraform.io`) for providers _without_ a registry definition. + +You can override this default with your own `packageRules`, for example: + +```json title="Prefer releases from OpenTofu" +{ + "packageRules": [ + { + "matchDatasources": ["terraform-provider"], + "registryUrl": "https://registry.opentofu.org" + } + ] +} +``` + ### Supported dependencies Renovate supports updating the Terraform dependencies listed below. @@ -102,4 +120,4 @@ You can use these `depTypes` for fine-grained control, for example to disable pa | Kubernetes StatefulSet | `kubernetes_stateful_set` | | | Kubernetes StatefulSet v1 | `kubernetes_stateful_set_v1` | | -If you need to change the versioning format, read the [versioning](../../versioning.md) documentation to learn more. +If you need to change the versioning format, read the [versioning](../../versioning/index.md) documentation to learn more. diff --git a/lib/modules/manager/terragrunt/__fixtures__/2.hcl b/lib/modules/manager/terragrunt/__fixtures__/2.hcl index b99906d626d704..523dd78b898881 100644 --- a/lib/modules/manager/terragrunt/__fixtures__/2.hcl +++ b/lib/modules/manager/terragrunt/__fixtures__/2.hcl @@ -127,32 +127,32 @@ terraform { # foobar terraform { - source = "https://bitbucket.com/hashicorp/example?ref=v1.0.0" + source = "https://mygit.com/hashicorp/example?ref=v1.0.0" } # gittags terraform { - source = "git::https://bitbucket.com/hashicorp/example?ref=v1.0.0" + source = "git::https://mygit.com/hashicorp/example?ref=v1.0.0" } # gittags_badversion terraform { - source = "git::https://bitbucket.com/hashicorp/example?ref=next" + source = "git::https://mygit.com/hashicorp/example?ref=next" } # gittags_subdir terraform { - source = "git::https://bitbucket.com/hashicorp/example//subdir/test?ref=v1.0.1" + source = "git::https://mygit.com/hashicorp/example//subdir/test?ref=v1.0.1" } # gittags_http terraform { - source = "git::http://bitbucket.com/hashicorp/example?ref=v1.0.2" + source = "git::http://mygit.com/hashicorp/example?ref=v1.0.2" } # gittags_ssh terraform { - source = "git::ssh://git@bitbucket.com/hashicorp/example?ref=v1.0.3" + source = "git::ssh://git@mygit.com/hashicorp/example?ref=v1.0.3" } # invalid, ignored by test since it does not have source on the next line @@ -164,3 +164,28 @@ terraform { name = "foo" dummy = "true" } + +# bitbucket-tags +terraform { + source = "git::https://bitbucket.com/hashicorp/example?ref=v1.0.0" +} + +# gitlab-tags +terraform { + source = "git::https://gitlab.com/hashicorp/example?ref=v1.0.0" +} + +# gitlab-tags https with custom port +terraform { + source = "git::https://gitlab.com:4321/hashicorp/example?ref=v1.0.1" +} + +# gitlab-tags ssh with custom port +terraform { + source = "git::ssh://gitlab.com:1234/hashicorp/example.git//foo/bar?ref=v1.0.2" +} + +# gitea-tags +terraform { + source = "git::https://gitea.com/hashicorp/example?ref=v1.0.0" +} diff --git a/lib/modules/manager/terragrunt/__fixtures__/3.hcl b/lib/modules/manager/terragrunt/__fixtures__/3.hcl index bfb076653db875..24e81c345cfc7b 100644 --- a/lib/modules/manager/terragrunt/__fixtures__/3.hcl +++ b/lib/modules/manager/terragrunt/__fixtures__/3.hcl @@ -127,32 +127,32 @@ terraform { # foobar terraform { - source = "https://bitbucket.com/hashicorp/example?ref=v1.0.0&depth=1" + source = "https://mygit.com/hashicorp/example?ref=v1.0.0&depth=1" } # gittags terraform { - source = "git::https://bitbucket.com/hashicorp/example?ref=v1.0.0&depth=1" + source = "git::https://mygit.com/hashicorp/example?ref=v1.0.0&depth=1" } # gittags_badversion terraform { - source = "git::https://bitbucket.com/hashicorp/example?ref=next&depth=1" + source = "git::https://mygit.com/hashicorp/example?ref=next&depth=1" } # gittags_subdir terraform { - source = "git::https://bitbucket.com/hashicorp/example//subdir/test?ref=v1.0.1&depth=1" + source = "git::https://mygit.com/hashicorp/example//subdir/test?ref=v1.0.1&depth=1" } # gittags_http terraform { - source = "git::http://bitbucket.com/hashicorp/example?ref=v1.0.2&depth=1" + source = "git::http://mygit.com/hashicorp/example?ref=v1.0.2&depth=1" } # gittags_ssh terraform { - source = "git::ssh://git@bitbucket.com/hashicorp/example?ref=v1.0.3&depth=1" + source = "git::ssh://git@mygit.com/hashicorp/example?ref=v1.0.3&depth=1" } # invalid, ignored by test since it does not have source on the next line @@ -164,3 +164,29 @@ terraform { name = "foo" dummy = "true" } + +# bitbucket-tags +terraform { + source = "git::https://bitbucket.com/hashicorp/example?ref=v1.0.0" +} + +# gitlab-tags +terraform { + source = "git::https://gitlab.com/hashicorp/example?ref=v1.0.0" +} + +# gitlab-tags https with custom port +terraform { + source = "git::https://gitlab.com:4321/hashicorp/example?ref=v1.0.1" +} + +# gitlab-tags ssh with custom port +terraform { + source = "git::ssh://gitlab.com:1234/hashicorp/example.git//foo/bar?ref=v1.0.2" +} + +# gitea-tags +terraform { + source = "git::https://gitea.com/hashicorp/example?ref=v1.0.0" +} + diff --git a/lib/modules/manager/terragrunt/__fixtures__/4.hcl b/lib/modules/manager/terragrunt/__fixtures__/4.hcl index 72889fb6aed347..8ac54753fe0d21 100644 --- a/lib/modules/manager/terragrunt/__fixtures__/4.hcl +++ b/lib/modules/manager/terragrunt/__fixtures__/4.hcl @@ -127,32 +127,32 @@ terraform { # foobar terraform { - source = "https://bitbucket.com/hashicorp/example?depth=1&ref=v1.0.0" + source = "https://mygit.com/hashicorp/example?depth=1&ref=v1.0.0" } # gittags terraform { - source = "git::https://bitbucket.com/hashicorp/example?depth=1&ref=v1.0.0" + source = "git::https://mygit.com/hashicorp/example?depth=1&ref=v1.0.0" } # gittags_badversion terraform { - source = "git::https://bitbucket.com/hashicorp/example?depth=1&ref=next" + source = "git::https://mygit.com/hashicorp/example?depth=1&ref=next" } # gittags_subdir terraform { - source = "git::https://bitbucket.com/hashicorp/example//subdir/test?depth=1&ref=v1.0.1" + source = "git::https://mygit.com/hashicorp/example//subdir/test?depth=1&ref=v1.0.1" } # gittags_http terraform { - source = "git::http://bitbucket.com/hashicorp/example?depth=1&ref=v1.0.2" + source = "git::http://mygit.com/hashicorp/example?depth=1&ref=v1.0.2" } # gittags_ssh terraform { - source = "git::ssh://git@bitbucket.com/hashicorp/example?depth=1&ref=v1.0.3" + source = "git::ssh://git@mygit.com/hashicorp/example?depth=1&ref=v1.0.3" } # invalid, ignored by test since it does not have source on the next line @@ -164,3 +164,30 @@ terraform { name = "foo" dummy = "true" } + + +# bitbucket-tags +terraform { + source = "git::https://bitbucket.com/hashicorp/example?ref=v1.0.0" +} + +# gitlab-tags +terraform { + source = "git::https://gitlab.com/hashicorp/example?ref=v1.0.0" +} + +# gitlab-tags https with custom port +terraform { + source = "git::https://gitlab.com:4321/hashicorp/example?ref=v1.0.1" +} + +# gitlab-tags ssh with custom port +terraform { + source = "git::ssh://gitlab.com:1234/hashicorp/example.git//foo/bar?ref=v1.0.2" +} + +# gitea-tags +terraform { + source = "git::https://gitea.com/hashicorp/example?ref=v1.0.0" +} + diff --git a/lib/modules/manager/terragrunt/extract.spec.ts b/lib/modules/manager/terragrunt/extract.spec.ts index bcba2edcbf9efc..c54270a9b00898 100644 --- a/lib/modules/manager/terragrunt/extract.spec.ts +++ b/lib/modules/manager/terragrunt/extract.spec.ts @@ -66,7 +66,13 @@ describe('modules/manager/terragrunt/extract', () => { depType: 'github', packageName: 'hashicorp/example', }, - {}, + { + currentValue: 'next', + datasource: 'git-tags', + depName: '104.196.242.174/example', + depType: 'gitTags', + packageName: 'https://104.196.242.174/example', + }, {}, { datasource: 'terraform-module', @@ -155,44 +161,44 @@ describe('modules/manager/terragrunt/extract', () => { { currentValue: 'v1.0.0', datasource: 'git-tags', - depName: 'bitbucket.com/hashicorp/example', + depName: 'mygit.com/hashicorp/example', depType: 'gitTags', - packageName: 'https://bitbucket.com/hashicorp/example', + packageName: 'https://mygit.com/hashicorp/example', }, { currentValue: 'v1.0.0', datasource: 'git-tags', - depName: 'bitbucket.com/hashicorp/example', + depName: 'mygit.com/hashicorp/example', depType: 'gitTags', - packageName: 'https://bitbucket.com/hashicorp/example', + packageName: 'https://mygit.com/hashicorp/example', }, { currentValue: 'next', datasource: 'git-tags', - depName: 'bitbucket.com/hashicorp/example', + depName: 'mygit.com/hashicorp/example', depType: 'gitTags', - packageName: 'https://bitbucket.com/hashicorp/example', + packageName: 'https://mygit.com/hashicorp/example', }, { currentValue: 'v1.0.1', datasource: 'git-tags', - depName: 'bitbucket.com/hashicorp/example', + depName: 'mygit.com/hashicorp/example', depType: 'gitTags', - packageName: 'https://bitbucket.com/hashicorp/example', + packageName: 'https://mygit.com/hashicorp/example', }, { currentValue: 'v1.0.2', datasource: 'git-tags', - depName: 'bitbucket.com/hashicorp/example', + depName: 'mygit.com/hashicorp/example', depType: 'gitTags', - packageName: 'http://bitbucket.com/hashicorp/example', + packageName: 'http://mygit.com/hashicorp/example', }, { currentValue: 'v1.0.3', datasource: 'git-tags', - depName: 'bitbucket.com/hashicorp/example', + depName: 'mygit.com/hashicorp/example', depType: 'gitTags', - packageName: 'ssh://git@bitbucket.com/hashicorp/example', + packageName: 'ssh://git@mygit.com/hashicorp/example', }, { skipReason: 'no-source', @@ -200,9 +206,49 @@ describe('modules/manager/terragrunt/extract', () => { { skipReason: 'no-source', }, + { + currentValue: 'v1.0.0', + datasource: 'bitbucket-tags', + depName: 'bitbucket.com/hashicorp/example', + depType: 'gitTags', + packageName: 'hashicorp/example', + registryUrls: ['https://bitbucket.com'], + }, + { + currentValue: 'v1.0.0', + datasource: 'gitlab-tags', + depName: 'gitlab.com/hashicorp/example', + depType: 'gitTags', + packageName: 'hashicorp/example', + registryUrls: ['https://gitlab.com'], + }, + { + currentValue: 'v1.0.1', + datasource: 'gitlab-tags', + depName: 'gitlab.com/hashicorp/example', + depType: 'gitTags', + packageName: 'hashicorp/example', + registryUrls: ['https://gitlab.com:4321'], + }, + { + currentValue: 'v1.0.2', + datasource: 'gitlab-tags', + depName: 'gitlab.com/hashicorp/example', + depType: 'gitTags', + packageName: 'hashicorp/example', + registryUrls: ['https://gitlab.com'], + }, + { + currentValue: 'v1.0.0', + datasource: 'gitea-tags', + depName: 'gitea.com/hashicorp/example', + depType: 'gitTags', + packageName: 'hashicorp/example', + registryUrls: ['https://gitea.com'], + }, ], }); - expect(res?.deps).toHaveLength(30); + expect(res?.deps).toHaveLength(35); expect(res?.deps.filter((dep) => dep.skipReason)).toHaveLength(4); }); @@ -231,7 +277,13 @@ describe('modules/manager/terragrunt/extract', () => { depType: 'github', packageName: 'hashicorp/example', }, - {}, + { + currentValue: 'next', + datasource: 'git-tags', + depName: '104.196.242.174/example', + depType: 'gitTags', + packageName: 'https://104.196.242.174/example', + }, {}, { datasource: 'terraform-module', @@ -320,44 +372,44 @@ describe('modules/manager/terragrunt/extract', () => { { currentValue: 'v1.0.0', datasource: 'git-tags', - depName: 'bitbucket.com/hashicorp/example', + depName: 'mygit.com/hashicorp/example', depType: 'gitTags', - packageName: 'https://bitbucket.com/hashicorp/example', + packageName: 'https://mygit.com/hashicorp/example', }, { currentValue: 'v1.0.0', datasource: 'git-tags', - depName: 'bitbucket.com/hashicorp/example', + depName: 'mygit.com/hashicorp/example', depType: 'gitTags', - packageName: 'https://bitbucket.com/hashicorp/example', + packageName: 'https://mygit.com/hashicorp/example', }, { currentValue: 'next', datasource: 'git-tags', - depName: 'bitbucket.com/hashicorp/example', + depName: 'mygit.com/hashicorp/example', depType: 'gitTags', - packageName: 'https://bitbucket.com/hashicorp/example', + packageName: 'https://mygit.com/hashicorp/example', }, { currentValue: 'v1.0.1', datasource: 'git-tags', - depName: 'bitbucket.com/hashicorp/example', + depName: 'mygit.com/hashicorp/example', depType: 'gitTags', - packageName: 'https://bitbucket.com/hashicorp/example', + packageName: 'https://mygit.com/hashicorp/example', }, { currentValue: 'v1.0.2', datasource: 'git-tags', - depName: 'bitbucket.com/hashicorp/example', + depName: 'mygit.com/hashicorp/example', depType: 'gitTags', - packageName: 'http://bitbucket.com/hashicorp/example', + packageName: 'http://mygit.com/hashicorp/example', }, { currentValue: 'v1.0.3', datasource: 'git-tags', - depName: 'bitbucket.com/hashicorp/example', + depName: 'mygit.com/hashicorp/example', depType: 'gitTags', - packageName: 'ssh://git@bitbucket.com/hashicorp/example', + packageName: 'ssh://git@mygit.com/hashicorp/example', }, { skipReason: 'no-source', @@ -365,9 +417,49 @@ describe('modules/manager/terragrunt/extract', () => { { skipReason: 'no-source', }, + { + currentValue: 'v1.0.0', + datasource: 'bitbucket-tags', + depName: 'bitbucket.com/hashicorp/example', + depType: 'gitTags', + packageName: 'hashicorp/example', + registryUrls: ['https://bitbucket.com'], + }, + { + currentValue: 'v1.0.0', + datasource: 'gitlab-tags', + depName: 'gitlab.com/hashicorp/example', + depType: 'gitTags', + packageName: 'hashicorp/example', + registryUrls: ['https://gitlab.com'], + }, + { + currentValue: 'v1.0.1', + datasource: 'gitlab-tags', + depName: 'gitlab.com/hashicorp/example', + depType: 'gitTags', + packageName: 'hashicorp/example', + registryUrls: ['https://gitlab.com:4321'], + }, + { + currentValue: 'v1.0.2', + datasource: 'gitlab-tags', + depName: 'gitlab.com/hashicorp/example', + depType: 'gitTags', + packageName: 'hashicorp/example', + registryUrls: ['https://gitlab.com'], + }, + { + currentValue: 'v1.0.0', + datasource: 'gitea-tags', + depName: 'gitea.com/hashicorp/example', + depType: 'gitTags', + packageName: 'hashicorp/example', + registryUrls: ['https://gitea.com'], + }, ], }); - expect(res?.deps).toHaveLength(30); + expect(res?.deps).toHaveLength(35); expect(res?.deps.filter((dep) => dep.skipReason)).toHaveLength(4); }); @@ -396,7 +488,13 @@ describe('modules/manager/terragrunt/extract', () => { depType: 'github', packageName: 'hashicorp/example', }, - {}, + { + currentValue: 'next', + datasource: 'git-tags', + depName: '104.196.242.174/example', + depType: 'gitTags', + packageName: 'https://104.196.242.174/example', + }, {}, { datasource: 'terraform-module', @@ -485,44 +583,44 @@ describe('modules/manager/terragrunt/extract', () => { { currentValue: 'v1.0.0', datasource: 'git-tags', - depName: 'bitbucket.com/hashicorp/example', + depName: 'mygit.com/hashicorp/example', depType: 'gitTags', - packageName: 'https://bitbucket.com/hashicorp/example', + packageName: 'https://mygit.com/hashicorp/example', }, { currentValue: 'v1.0.0', datasource: 'git-tags', - depName: 'bitbucket.com/hashicorp/example', + depName: 'mygit.com/hashicorp/example', depType: 'gitTags', - packageName: 'https://bitbucket.com/hashicorp/example', + packageName: 'https://mygit.com/hashicorp/example', }, { currentValue: 'next', datasource: 'git-tags', - depName: 'bitbucket.com/hashicorp/example', + depName: 'mygit.com/hashicorp/example', depType: 'gitTags', - packageName: 'https://bitbucket.com/hashicorp/example', + packageName: 'https://mygit.com/hashicorp/example', }, { currentValue: 'v1.0.1', datasource: 'git-tags', - depName: 'bitbucket.com/hashicorp/example', + depName: 'mygit.com/hashicorp/example', depType: 'gitTags', - packageName: 'https://bitbucket.com/hashicorp/example', + packageName: 'https://mygit.com/hashicorp/example', }, { currentValue: 'v1.0.2', datasource: 'git-tags', - depName: 'bitbucket.com/hashicorp/example', + depName: 'mygit.com/hashicorp/example', depType: 'gitTags', - packageName: 'http://bitbucket.com/hashicorp/example', + packageName: 'http://mygit.com/hashicorp/example', }, { currentValue: 'v1.0.3', datasource: 'git-tags', - depName: 'bitbucket.com/hashicorp/example', + depName: 'mygit.com/hashicorp/example', depType: 'gitTags', - packageName: 'ssh://git@bitbucket.com/hashicorp/example', + packageName: 'ssh://git@mygit.com/hashicorp/example', }, { skipReason: 'no-source', @@ -530,9 +628,49 @@ describe('modules/manager/terragrunt/extract', () => { { skipReason: 'no-source', }, + { + currentValue: 'v1.0.0', + datasource: 'bitbucket-tags', + depName: 'bitbucket.com/hashicorp/example', + depType: 'gitTags', + packageName: 'hashicorp/example', + registryUrls: ['https://bitbucket.com'], + }, + { + currentValue: 'v1.0.0', + datasource: 'gitlab-tags', + depName: 'gitlab.com/hashicorp/example', + depType: 'gitTags', + packageName: 'hashicorp/example', + registryUrls: ['https://gitlab.com'], + }, + { + currentValue: 'v1.0.1', + datasource: 'gitlab-tags', + depName: 'gitlab.com/hashicorp/example', + depType: 'gitTags', + packageName: 'hashicorp/example', + registryUrls: ['https://gitlab.com:4321'], + }, + { + currentValue: 'v1.0.2', + datasource: 'gitlab-tags', + depName: 'gitlab.com/hashicorp/example', + depType: 'gitTags', + packageName: 'hashicorp/example', + registryUrls: ['https://gitlab.com'], + }, + { + currentValue: 'v1.0.0', + datasource: 'gitea-tags', + depName: 'gitea.com/hashicorp/example', + depType: 'gitTags', + packageName: 'hashicorp/example', + registryUrls: ['https://gitea.com'], + }, ], }); - expect(res?.deps).toHaveLength(30); + expect(res?.deps).toHaveLength(35); expect(res?.deps.filter((dep) => dep.skipReason)).toHaveLength(4); }); diff --git a/lib/modules/manager/terragrunt/index.ts b/lib/modules/manager/terragrunt/index.ts index 58864cf7f57085..9343bcd0c5e8ae 100644 --- a/lib/modules/manager/terragrunt/index.ts +++ b/lib/modules/manager/terragrunt/index.ts @@ -1,6 +1,9 @@ import type { Category } from '../../../constants'; +import { BitbucketTagsDatasource } from '../../datasource/bitbucket-tags'; import { GitTagsDatasource } from '../../datasource/git-tags'; +import { GiteaTagsDatasource } from '../../datasource/gitea-tags'; import { GithubTagsDatasource } from '../../datasource/github-tags'; +import { GitlabTagsDatasource } from '../../datasource/gitlab-tags'; import { TerraformModuleDatasource } from '../../datasource/terraform-module'; export { updateArtifacts } from './artifacts'; @@ -9,6 +12,9 @@ export { extractPackageFile } from './extract'; export const supportedDatasources = [ GitTagsDatasource.id, GithubTagsDatasource.id, + GitlabTagsDatasource.id, + BitbucketTagsDatasource.id, + GiteaTagsDatasource.id, TerraformModuleDatasource.id, ]; diff --git a/lib/modules/manager/terragrunt/modules.spec.ts b/lib/modules/manager/terragrunt/modules.spec.ts index b2b30361860704..40d9b806c9f03d 100644 --- a/lib/modules/manager/terragrunt/modules.spec.ts +++ b/lib/modules/manager/terragrunt/modules.spec.ts @@ -24,7 +24,7 @@ describe('modules/manager/terragrunt/modules', () => { }); describe('gitTagsRefMatchRegex', () => { - it('should split project and tag from source', () => { + it('should split host, path and tag from source', () => { const http = gitTagsRefMatchRegex.exec( 'http://github.com/hashicorp/example?ref=v1.0.0', )?.groups; @@ -36,15 +36,18 @@ describe('modules/manager/terragrunt/modules', () => { )?.groups; expect(http).toMatchObject({ - project: 'hashicorp/example', + host: 'github.com', + path: 'hashicorp/example', tag: 'v1.0.0', }); expect(https).toMatchObject({ - project: 'hashicorp/example', + host: 'github.com', + path: 'hashicorp/example', tag: 'v1.0.0', }); expect(ssh).toMatchObject({ - project: 'hashicorp/example', + host: 'github.com', + path: 'hashicorp/example', tag: 'v1.0.0', }); }); @@ -61,15 +64,15 @@ describe('modules/manager/terragrunt/modules', () => { )?.groups; expect(http).toMatchObject({ - project: 'hashicorp/example.repo-123', + path: 'hashicorp/example.repo-123', tag: 'v1.0.0', }); expect(https).toMatchObject({ - project: 'hashicorp/example.repo-123', + path: 'hashicorp/example.repo-123', tag: 'v1.0.0', }); expect(ssh).toMatchObject({ - project: 'hashicorp/example.repo-123', + path: 'hashicorp/example.repo-123', tag: 'v1.0.0', }); }); diff --git a/lib/modules/manager/terragrunt/modules.ts b/lib/modules/manager/terragrunt/modules.ts index 01fc5b43562569..9d71e1e8aa6483 100644 --- a/lib/modules/manager/terragrunt/modules.ts +++ b/lib/modules/manager/terragrunt/modules.ts @@ -1,7 +1,11 @@ import { logger } from '../../../logger'; +import { detectPlatform } from '../../../util/common'; import { regEx } from '../../../util/regex'; +import { BitbucketTagsDatasource } from '../../datasource/bitbucket-tags'; import { GitTagsDatasource } from '../../datasource/git-tags'; +import { GiteaTagsDatasource } from '../../datasource/gitea-tags'; import { GithubTagsDatasource } from '../../datasource/github-tags'; +import { GitlabTagsDatasource } from '../../datasource/gitlab-tags'; import { TerraformModuleDatasource } from '../../datasource/terraform-module'; import type { PackageDependency } from '../types'; import { extractTerragruntProvider } from './providers'; @@ -11,7 +15,7 @@ export const githubRefMatchRegex = regEx( /github\.com([/:])(?[^/]+\/[a-z0-9-_.]+).*\?(depth=\d+&)?ref=(?.*?)(&depth=\d+)?$/i, ); export const gitTagsRefMatchRegex = regEx( - /(?:git::)?(?(?:http|https|ssh):\/\/(?:.*@)?(?.*.*\/(?.*\/.*)))\?(depth=\d+&)?ref=(?.*?)(&depth=\d+)?$/, + /(?:git::)?(?(?:http|https|ssh):\/\/(?:.*@)?(?[^/]*)\/(?.*))\?(depth=\d+&)?ref=(?.*?)(&depth=\d+)?$/, ); export const tfrVersionMatchRegex = regEx( /tfr:\/\/(?.*?)\/(?[^/]+?)\/(?[^/]+?)\/(?[^/?]+).*\?(?:ref|version)=(?.*?)$/, @@ -31,6 +35,20 @@ export function extractTerragruntModule( return result; } +function detectGitTagDatasource(registryUrl: string): string { + const platform = detectPlatform(registryUrl); + switch (platform) { + case 'gitlab': + return GitlabTagsDatasource.id; + case 'bitbucket': + return BitbucketTagsDatasource.id; + case 'gitea': + return GiteaTagsDatasource.id; + default: + return GitTagsDatasource.id; + } +} + export function analyseTerragruntModule( dep: PackageDependency, ): void { @@ -50,18 +68,34 @@ export function analyseTerragruntModule( dep.currentValue = githubRefMatch.groups.tag; dep.datasource = GithubTagsDatasource.id; } else if (gitTagsRefMatch?.groups) { - dep.depType = 'gitTags'; - if (gitTagsRefMatch.groups.path.includes('//')) { + const { url, tag } = gitTagsRefMatch.groups; + const { hostname, host, origin, pathname, protocol } = new URL(url); + const containsSubDirectory = pathname.includes('//'); + if (containsSubDirectory) { logger.debug('Terragrunt module contains subdirectory'); - dep.depName = gitTagsRefMatch.groups.path.split('//')[0]; - const tempLookupName = gitTagsRefMatch.groups.url.split('//'); - dep.packageName = tempLookupName[0] + '//' + tempLookupName[1]; + } + dep.depType = 'gitTags'; + // We don't want to have leading slash, .git or subdirectory in the repository path + const repositoryPath = pathname + .replace(regEx(/^\//), '') + .split('//')[0] + .replace(regEx('.git$'), ''); + dep.depName = `${hostname}/${repositoryPath}`; + dep.currentValue = tag; + dep.datasource = detectGitTagDatasource(url); + if (dep.datasource === GitTagsDatasource.id) { + if (containsSubDirectory) { + dep.packageName = `${origin}${pathname.split('//')[0]}`; + } else { + dep.packageName = url; + } } else { - dep.depName = gitTagsRefMatch.groups.path.replace('.git', ''); - dep.packageName = gitTagsRefMatch.groups.url; + // The packageName should only contain the path to the repository + dep.packageName = repositoryPath; + dep.registryUrls = [ + protocol === 'https:' ? `https://${host}` : `https://${hostname}`, + ]; } - dep.currentValue = gitTagsRefMatch.groups.tag; - dep.datasource = GitTagsDatasource.id; } else if (tfrVersionMatch?.groups) { dep.depType = 'terragrunt'; dep.depName = diff --git a/lib/modules/manager/types.ts b/lib/modules/manager/types.ts index 19f62b5375a0f5..b6a93703f254f4 100644 --- a/lib/modules/manager/types.ts +++ b/lib/modules/manager/types.ts @@ -22,6 +22,7 @@ export interface ExtractConfig extends CustomExtractConfig { npmrc?: string; npmrcMerge?: boolean; skipInstalls?: boolean | null; + repository?: string; } export interface UpdateArtifactsConfig { @@ -100,6 +101,10 @@ export interface LookupUpdate { registryUrl?: string; } +/** + * @property {string} depName - Display name of the package. See #16012 + * @property {string} packageName - The name of the package, used in comparisons. depName is used as fallback if this is not set. See #16012 + */ export interface PackageDependency> extends ManagerData { currentValue?: string | null; @@ -177,15 +182,27 @@ export interface Upgrade> extends PackageDependency { replaceString?: string; } +export interface ArtifactNotice { + file: string; + message: string; +} + export interface ArtifactError { lockFile?: string; stderr?: string; } -export interface UpdateArtifactsResult { - artifactError?: ArtifactError; - file?: FileChange; -} +export type UpdateArtifactsResult = + | { + file?: FileChange; + notice?: ArtifactNotice; + artifactError?: undefined; + } + | { + file?: undefined; + notice?: undefined; + artifactError?: ArtifactError; + }; export interface UpdateArtifact> { packageFileName: string; diff --git a/lib/modules/manager/velaci/readme.md b/lib/modules/manager/velaci/readme.md index 1436da23ba6008..8138ed8424efe9 100644 --- a/lib/modules/manager/velaci/readme.md +++ b/lib/modules/manager/velaci/readme.md @@ -1,3 +1,3 @@ Extracts Docker-type dependencies from VelaCI config files. -If you need to change the versioning format, read the [versioning](../../versioning.md) documentation to learn more. +If you need to change the versioning format, read the [versioning](../../versioning/index.md) documentation to learn more. diff --git a/lib/modules/manager/vendir/__fixtures__/invalid-contents.yaml b/lib/modules/manager/vendir/__fixtures__/invalid-contents.yaml new file mode 100644 index 00000000000000..fe23043bf1b11a --- /dev/null +++ b/lib/modules/manager/vendir/__fixtures__/invalid-contents.yaml @@ -0,0 +1,12 @@ +apiVersion: vendir.k14s.io/v1alpha1 +kind: Config +directories: +- path: vendor + contents: + - path: github.com/cloudfoundry/cf-k8s-networking + invalid: + # http or ssh urls are supported (required) + url: https://github.com/cloudfoundry/cf-k8s-networking + # branch, tag, commit; origin is the name of the remote (required) + # optional if refSelection is specified (available in v0.11.0+) + ref: origin/master diff --git a/lib/modules/manager/vendir/__fixtures__/invalid-vendir.yaml b/lib/modules/manager/vendir/__fixtures__/invalid-vendir.yaml new file mode 100644 index 00000000000000..534ff2789703ec --- /dev/null +++ b/lib/modules/manager/vendir/__fixtures__/invalid-vendir.yaml @@ -0,0 +1,12 @@ +apiVersion: vendir.k14s.io/v1alpha1 +kind: Config +directories: +- path: vendor + contents: + - path: github.com/cloudfoundry/cf-k8s-networking + test: + # http or ssh urls are supported (required) + url: https://github.com/cloudfoundry/cf-k8s-networking + # branch, tag, commit; origin is the name of the remote (required) + # optional if refSelection is specified (available in v0.11.0+) + ref: origin/master diff --git a/lib/modules/manager/vendir/__fixtures__/valid-contents.yaml b/lib/modules/manager/vendir/__fixtures__/valid-contents.yaml new file mode 100644 index 00000000000000..23f317ea994d35 --- /dev/null +++ b/lib/modules/manager/vendir/__fixtures__/valid-contents.yaml @@ -0,0 +1,43 @@ +apiVersion: vendir.k14s.io/v1alpha1 +kind: Config +directories: +- path: vendor + contents: + # Normal Helm Chart + - path: custom-repo-custom-version + helmChart: + name: valid-helmchart-1 + version: "7.10.1" + repository: + url: https://charts.bitnami.com/bitnami + # Normal Helm Chart 2 for handling lists + - path: thing + helmChart: + name: valid-helmchart-2 + version: "7.10.1" + repository: + url: https://charts.bitnami.com/bitnami + # OCI Helm Chart + - path: custom-repo-custom-version + helmChart: + name: oci-chart + version: "7.10.1" + repository: + url: oci://charts.bitnami.com/bitnami + # Aliased OCI Helm Chart + - path: custom-repo-custom-version + helmChart: + name: aliased-oci-chart + version: "7.10.1" + repository: + url: oci://test + # Normal Git Repo + - path: custom-repo-custom-version + git: + url: https://github.com/test/test + ref: "7.10.1" + # Normal GithubRelease Repo + - path: custom-repo-custom-version + githubRelease: + slug: test/test + tag: "7.10.1" diff --git a/lib/modules/manager/vendir/__fixtures__/valid-vendir.yaml b/lib/modules/manager/vendir/__fixtures__/valid-vendir.yaml new file mode 100644 index 00000000000000..3635b4f6873742 --- /dev/null +++ b/lib/modules/manager/vendir/__fixtures__/valid-vendir.yaml @@ -0,0 +1,38 @@ +apiVersion: vendir.k14s.io/v1alpha1 +kind: Config +directories: +- path: vendor + contents: + # Normal Helm Chart + - path: custom-repo-custom-version + helmChart: + name: contour + version: "7.10.1" + repository: + url: https://charts.bitnami.com/bitnami + # Normal Helm Chart 2 for handling lists + - path: thing + helmChart: + name: test + version: "7.10.1" + repository: + url: https://charts.bitnami.com/bitnami + # Normal Git Repo + - path: custom-repo-custom-version + git: + url: https://github.com/test/test + ref: "7.10.1" + # OCI Helm Chart + - path: custom-repo-custom-version + helmChart: + name: contour + version: "7.10.1" + repository: + url: oci://charts.bitnami.com/bitnami + # Aliased OCI Helm Chart + - path: custom-repo-custom-version + helmChart: + name: oci + version: "7.10.1" + repository: + url: oci://test diff --git a/lib/modules/manager/vendir/__fixtures__/vendir.yml b/lib/modules/manager/vendir/__fixtures__/vendir.yml new file mode 100644 index 00000000000000..ca496ff089bf08 --- /dev/null +++ b/lib/modules/manager/vendir/__fixtures__/vendir.yml @@ -0,0 +1,15 @@ +apiVersion: vendir.k14s.io/v1alpha1 +kind: Config + +minimumRequiredVersion: 0.32.0 + +# one or more directories to manage with vendir +directories: + - path: vendor + contents: + - path: renovate + helmChart: + name: renovate + version: 36.109.4 + repository: + url: https://docs.renovatebot.com/helm-charts diff --git a/lib/modules/manager/vendir/__fixtures__/vendir_1.lock b/lib/modules/manager/vendir/__fixtures__/vendir_1.lock new file mode 100644 index 00000000000000..49242c2e9efae3 --- /dev/null +++ b/lib/modules/manager/vendir/__fixtures__/vendir_1.lock @@ -0,0 +1,9 @@ +apiVersion: vendir.k14s.io/v1alpha1 +directories: +- contents: + - helmChart: + appVersion: 36.109.4 + version: 36.109.4 + path: renovate + path: vendor +kind: LockConfig diff --git a/lib/modules/manager/vendir/__fixtures__/vendir_2.lock b/lib/modules/manager/vendir/__fixtures__/vendir_2.lock new file mode 100644 index 00000000000000..76db684b7cd8bf --- /dev/null +++ b/lib/modules/manager/vendir/__fixtures__/vendir_2.lock @@ -0,0 +1,9 @@ +apiVersion: vendir.k14s.io/v1alpha1 +directories: +- contents: + - helmChart: + appVersion: 36.109.4 + version: 37.109.4 + path: renovate + path: vendor +kind: LockConfig diff --git a/lib/modules/manager/vendir/__snapshots__/artifacts.spec.ts.snap b/lib/modules/manager/vendir/__snapshots__/artifacts.spec.ts.snap new file mode 100644 index 00000000000000..059191726c0413 --- /dev/null +++ b/lib/modules/manager/vendir/__snapshots__/artifacts.spec.ts.snap @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`modules/manager/vendir/artifacts returns null if unchanged 1`] = ` +[ + { + "cmd": "vendir sync", + "options": { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; diff --git a/lib/modules/manager/vendir/artifacts.spec.ts b/lib/modules/manager/vendir/artifacts.spec.ts new file mode 100644 index 00000000000000..8726fe3a31c546 --- /dev/null +++ b/lib/modules/manager/vendir/artifacts.spec.ts @@ -0,0 +1,513 @@ +import { mockDeep } from 'jest-mock-extended'; +import { join } from 'upath'; +import { envMock, mockExecAll } from '../../../../test/exec-util'; +import { Fixtures } from '../../../../test/fixtures'; +import { env, fs, git, partial } from '../../../../test/util'; +import { GlobalConfig } from '../../../config/global'; +import type { RepoGlobalConfig } from '../../../config/types'; +import { TEMPORARY_ERROR } from '../../../constants/error-messages'; +import { ExecError } from '../../../util/exec/exec-error'; +import type { StatusResult } from '../../../util/git/types'; +import type { UpdateArtifactsConfig } from '../types'; +import * as vendir from '.'; + +process.env.CONTAINERBASE = 'true'; + +jest.mock('../../datasource', () => mockDeep()); +jest.mock('../../../util/exec/env', () => mockDeep()); +jest.mock('../../../util/http', () => mockDeep()); +jest.mock('../../../util/fs', () => mockDeep()); +jest.mock('../../../util/git', () => mockDeep()); + +const adminConfig: RepoGlobalConfig = { + localDir: join('/tmp/github/some/repo'), // `join` fixes Windows CI + cacheDir: join('/tmp/renovate/cache'), + containerbaseDir: join('/tmp/cache/containerbase'), + dockerSidecarImage: 'ghcr.io/containerbase/sidecar', +}; + +const config: UpdateArtifactsConfig = {}; +const vendirLockFile1 = Fixtures.get('vendir_1.lock'); +const vendirLockFile2 = Fixtures.get('vendir_2.lock'); +const vendirFile = Fixtures.get('vendir.yml'); + +describe('modules/manager/vendir/artifacts', () => { + beforeEach(() => { + env.getChildProcessEnv.mockReturnValue(envMock.basic); + GlobalConfig.set(adminConfig); + }); + + it('returns null if no vendir.lock.yml found', async () => { + const updatedDeps = [{ depName: 'dep1' }]; + expect( + await vendir.updateArtifacts({ + packageFileName: 'vendir.yml', + updatedDeps, + newPackageFileContent: '', + config, + }), + ).toBeNull(); + }); + + it('returns null if empty vendir.lock.yml found', async () => { + const updatedDeps = [{ depName: 'dep1' }]; + fs.readLocalFile.mockResolvedValueOnce(''); + fs.getSiblingFileName.mockReturnValueOnce('vendir.lock.yml'); + expect( + await vendir.updateArtifacts({ + packageFileName: 'vendir.yml', + updatedDeps, + newPackageFileContent: '', + config, + }), + ).toBeNull(); + }); + + it('returns null if updatedDeps is empty', async () => { + expect( + await vendir.updateArtifacts({ + packageFileName: 'vendir.lock.yml', + updatedDeps: [], + newPackageFileContent: '', + config, + }), + ).toBeNull(); + }); + + it('returns null if unchanged', async () => { + fs.readLocalFile.mockResolvedValueOnce(vendirLockFile1); + fs.getSiblingFileName.mockReturnValueOnce(vendirFile); + fs.getSiblingFileName.mockReturnValueOnce('vendir.lock.yml'); + const execSnapshots = mockExecAll(); + fs.readLocalFile.mockResolvedValueOnce(vendirLockFile1); + fs.privateCacheDir.mockReturnValue( + '/tmp/renovate/cache/__renovate-private-cache', + ); + fs.getParentDir.mockReturnValue(''); + const updatedDeps = [{ depName: 'dep1' }]; + expect( + await vendir.updateArtifacts({ + packageFileName: 'vendir.yml', + updatedDeps, + newPackageFileContent: vendirFile, + config, + }), + ).toBeNull(); + expect(execSnapshots).toMatchSnapshot([{ cmd: 'vendir sync' }]); + }); + + it('returns updated vendir.lock', async () => { + fs.readLocalFile.mockResolvedValueOnce(vendirLockFile1); + fs.getSiblingFileName.mockReturnValueOnce('vendir.lock.yml'); + const execSnapshots = mockExecAll(); + fs.readLocalFile.mockResolvedValueOnce(vendirLockFile2); + fs.privateCacheDir.mockReturnValue( + '/tmp/renovate/cache/__renovate-private-cache', + ); + fs.getParentDir.mockReturnValue(''); + const updatedDeps = [{ depName: 'dep1' }]; + expect( + await vendir.updateArtifacts({ + packageFileName: 'vendir.yml', + updatedDeps, + newPackageFileContent: vendirFile, + config, + }), + ).toEqual([ + { + file: { + type: 'addition', + path: 'vendir.lock.yml', + contents: vendirLockFile2, + }, + }, + ]); + expect(execSnapshots).toMatchObject([{ cmd: 'vendir sync' }]); + }); + + it('returns updated vendir.yml for lockfile maintenance', async () => { + fs.readLocalFile.mockResolvedValueOnce(vendirLockFile1); + fs.getSiblingFileName.mockReturnValueOnce('vendir.yml'); + const execSnapshots = mockExecAll(); + fs.readLocalFile.mockResolvedValueOnce(vendirLockFile2); + fs.privateCacheDir.mockReturnValue( + '/tmp/renovate/cache/__renovate-private-cache', + ); + fs.getParentDir.mockReturnValue(''); + expect( + await vendir.updateArtifacts({ + packageFileName: 'vendir.yml', + updatedDeps: [], + newPackageFileContent: vendirFile, + config: { ...config, updateType: 'lockFileMaintenance' }, + }), + ).toEqual([ + { + file: { + type: 'addition', + path: 'vendir.yml', + contents: vendirLockFile2, + }, + }, + ]); + expect(execSnapshots).toMatchObject([{ cmd: 'vendir sync' }]); + }); + + it('catches errors', async () => { + fs.getSiblingFileName.mockReturnValueOnce('vendir.yml'); + fs.readLocalFile.mockResolvedValueOnce(vendirLockFile1); + fs.privateCacheDir.mockReturnValue( + '/tmp/renovate/cache/__renovate-private-cache', + ); + fs.writeLocalFile.mockImplementationOnce(() => { + throw new Error('not found'); + }); + const updatedDeps = [{ depName: 'dep1' }]; + expect( + await vendir.updateArtifacts({ + packageFileName: 'vendir.yml', + updatedDeps, + newPackageFileContent: vendirFile, + config, + }), + ).toEqual([ + { + artifactError: { + lockFile: 'vendir.yml', + stderr: 'not found', + }, + }, + ]); + }); + + it('rethrows for temporary error', async () => { + fs.readLocalFile.mockResolvedValueOnce(vendirLockFile1); + fs.getSiblingFileName.mockReturnValueOnce('vendir.yml'); + fs.readLocalFile.mockResolvedValueOnce(vendirLockFile2); + fs.privateCacheDir.mockReturnValue( + '/tmp/renovate/cache/__renovate-private-cache', + ); + fs.getParentDir.mockReturnValue(''); + const execError = new ExecError(TEMPORARY_ERROR, { + cmd: '', + stdout: '', + stderr: '', + options: { encoding: 'utf8' }, + }); + const updatedDeps = [{ depName: 'dep1' }]; + mockExecAll(execError); + await expect( + vendir.updateArtifacts({ + packageFileName: 'vendir.yml', + updatedDeps, + newPackageFileContent: vendirFile, + config, + }), + ).rejects.toThrow(TEMPORARY_ERROR); + }); + + it('add artifacts to file list if vendir.yml exists', async () => { + fs.readLocalFile.mockResolvedValueOnce(vendirLockFile1); + fs.getSiblingFileName.mockReturnValueOnce('vendir.lock.yml'); + const execSnapshots = mockExecAll(); + fs.readLocalFile.mockResolvedValueOnce(vendirLockFile2); + fs.privateCacheDir.mockReturnValue( + '/tmp/renovate/cache/__renovate-private-cache', + ); + fs.getParentDir.mockReturnValue(''); + + // artifacts + fs.getSiblingFileName.mockReturnValueOnce('vendor'); + git.getRepoStatus.mockResolvedValueOnce( + partial({ + not_added: ['vendor/Chart.yaml', 'vendor/my-chart/Chart.yaml'], + deleted: ['vendor/removed.yaml'], + }), + ); + const updatedDeps = [{ depName: 'dep1' }]; + const test = await vendir.updateArtifacts({ + packageFileName: 'vendir.yml', + updatedDeps, + newPackageFileContent: vendirFile, + config: { + ...config, + }, + }); + expect(test).toEqual([ + { + file: { + type: 'addition', + path: 'vendir.lock.yml', + contents: vendirLockFile2, + }, + }, + { + file: { + type: 'addition', + path: 'vendor/Chart.yaml', + contents: undefined, + }, + }, + { + file: { + type: 'addition', + path: 'vendor/my-chart/Chart.yaml', + contents: undefined, + }, + }, + { + file: { + type: 'deletion', + path: 'vendor/removed.yaml', + }, + }, + ]); + expect(execSnapshots).toMatchObject([ + { + cmd: 'vendir sync', + options: { + env: { + HOME: '/home/user', + HTTPS_PROXY: 'https://example.com', + HTTP_PROXY: 'http://example.com', + LANG: 'en_US.UTF-8', + LC_ALL: 'en_US', + NO_PROXY: 'localhost', + PATH: '/tmp/path', + }, + }, + }, + ]); + }); + + it('add artifacts', async () => { + fs.readLocalFile.mockResolvedValueOnce(vendirLockFile1); + fs.getSiblingFileName.mockReturnValueOnce('vendir.lock.yml'); + fs.readLocalFile.mockResolvedValueOnce(vendirLockFile2); + const execSnapshots = mockExecAll(); + fs.privateCacheDir.mockReturnValue( + '/tmp/renovate/cache/__renovate-private-cache', + ); + fs.getParentDir.mockReturnValue(''); + + // artifacts + fs.getSiblingFileName.mockReturnValueOnce('vendor'); + git.getRepoStatus.mockResolvedValueOnce( + partial({ + not_added: ['vendor/Chart.yaml'], + }), + ); + const updatedDeps = [{ depName: 'dep1' }]; + expect( + await vendir.updateArtifacts({ + packageFileName: 'vendir.yml', + updatedDeps, + newPackageFileContent: vendirFile, + config: { + ...config, + }, + }), + ).toEqual([ + { + file: { + type: 'addition', + path: 'vendir.lock.yml', + contents: vendirLockFile2, + }, + }, + { + file: { + type: 'addition', + path: 'vendor/Chart.yaml', + contents: undefined, + }, + }, + ]); + expect(execSnapshots).toMatchObject([ + { + cmd: 'vendir sync', + options: { + env: { + HOME: '/home/user', + HTTPS_PROXY: 'https://example.com', + HTTP_PROXY: 'http://example.com', + LANG: 'en_US.UTF-8', + LC_ALL: 'en_US', + NO_PROXY: 'localhost', + PATH: '/tmp/path', + }, + }, + }, + ]); + }); + + it('works explicit global binarySource', async () => { + GlobalConfig.set({ ...adminConfig, binarySource: 'global' }); + fs.readLocalFile.mockResolvedValueOnce(vendirLockFile1); + fs.getSiblingFileName.mockReturnValueOnce('vendir.lock.yml'); + const execSnapshots = mockExecAll(); + fs.readLocalFile.mockResolvedValueOnce(vendirLockFile2); + fs.privateCacheDir.mockReturnValue( + '/tmp/renovate/cache/__renovate-private-cache', + ); + fs.getParentDir.mockReturnValue(''); + const updatedDeps = [{ depName: 'dep1' }]; + expect( + await vendir.updateArtifacts({ + packageFileName: 'vendir.yml', + updatedDeps, + newPackageFileContent: vendirFile, + config, + }), + ).toEqual([ + { + file: { + type: 'addition', + path: 'vendir.lock.yml', + contents: vendirLockFile2, + }, + }, + ]); + expect(execSnapshots).toMatchObject([{ cmd: 'vendir sync' }]); + }); + + it('supports install mode', async () => { + GlobalConfig.set({ + ...adminConfig, + binarySource: 'install', + }); + fs.readLocalFile.mockResolvedValueOnce(vendirLockFile1); + fs.getSiblingFileName.mockReturnValueOnce('vendir.lock.yml'); + fs.readLocalFile.mockResolvedValueOnce(vendirLockFile2); + const execSnapshots = mockExecAll(); + fs.privateCacheDir.mockReturnValue( + '/tmp/renovate/cache/__renovate-private-cache', + ); + fs.getParentDir.mockReturnValue(''); + const updatedDeps = [{ depName: 'dep1' }]; + expect( + await vendir.updateArtifacts({ + packageFileName: 'vendir.yml', + updatedDeps, + newPackageFileContent: vendirFile, + config: { + ...config, + constraints: { vendir: '0.35.0', helm: '3.17.0' }, + }, + }), + ).toEqual([ + { + file: { + type: 'addition', + path: 'vendir.lock.yml', + contents: vendirLockFile2, + }, + }, + ]); + expect(execSnapshots).toMatchObject([ + { + cmd: 'install-tool vendir 0.35.0', + options: { + env: { + HOME: '/home/user', + HTTPS_PROXY: 'https://example.com', + HTTP_PROXY: 'http://example.com', + LANG: 'en_US.UTF-8', + LC_ALL: 'en_US', + NO_PROXY: 'localhost', + PATH: '/tmp/path', + }, + }, + }, + { + cmd: 'install-tool helm 3.17.0', + options: { + env: { + HOME: '/home/user', + HTTPS_PROXY: 'https://example.com', + HTTP_PROXY: 'http://example.com', + LANG: 'en_US.UTF-8', + LC_ALL: 'en_US', + NO_PROXY: 'localhost', + PATH: '/tmp/path', + }, + }, + }, + { + cmd: 'vendir sync', + options: { + env: { + HOME: '/home/user', + HTTPS_PROXY: 'https://example.com', + HTTP_PROXY: 'http://example.com', + LANG: 'en_US.UTF-8', + LC_ALL: 'en_US', + NO_PROXY: 'localhost', + PATH: '/tmp/path', + }, + }, + }, + ]); + }); + + describe('Docker', () => { + beforeEach(() => { + GlobalConfig.set({ + ...adminConfig, + binarySource: 'docker', + }); + }); + + it('returns updated vendir.yml for lockfile maintenance', async () => { + fs.readLocalFile.mockResolvedValueOnce(vendirLockFile1); + fs.getSiblingFileName.mockReturnValueOnce('vendir.lock.yml'); + fs.readLocalFile.mockResolvedValueOnce(vendirLockFile2); + const execSnapshots = mockExecAll(); + fs.privateCacheDir.mockReturnValue( + '/tmp/renovate/cache/__renovate-private-cache', + ); + fs.getParentDir.mockReturnValue(''); + const updatedDeps = [{ depName: 'dep1' }]; + expect( + await vendir.updateArtifacts({ + packageFileName: 'vendir.yml', + updatedDeps, + newPackageFileContent: vendirFile, + config: { + ...config, + constraints: { vendir: '0.35.0', helm: '3.17.0' }, + }, + }), + ).toEqual([ + { + file: { + type: 'addition', + path: 'vendir.lock.yml', + contents: vendirLockFile2, + }, + }, + ]); + expect(execSnapshots).toMatchObject([ + { cmd: 'docker pull ghcr.io/containerbase/sidecar' }, + { cmd: 'docker ps --filter name=renovate_sidecar -aq' }, + { + cmd: + 'docker run --rm --name=renovate_sidecar --label=renovate_child ' + + '-v "/tmp/github/some/repo":"/tmp/github/some/repo" ' + + '-v "/tmp/renovate/cache":"/tmp/renovate/cache" ' + + '-v "/tmp/cache/containerbase":"/tmp/cache/containerbase" ' + + '-e CONTAINERBASE_CACHE_DIR ' + + '-w "/tmp/github/some/repo" ' + + 'ghcr.io/containerbase/sidecar' + + ' bash -l -c "' + + 'install-tool vendir 0.35.0' + + ' && ' + + 'install-tool helm 3.17.0' + + ' && ' + + 'vendir sync' + + '"', + }, + ]); + }); + }); +}); diff --git a/lib/modules/manager/vendir/artifacts.ts b/lib/modules/manager/vendir/artifacts.ts new file mode 100644 index 00000000000000..17db506dd5d7b9 --- /dev/null +++ b/lib/modules/manager/vendir/artifacts.ts @@ -0,0 +1,113 @@ +import { TEMPORARY_ERROR } from '../../../constants/error-messages'; +import { logger } from '../../../logger'; +import { exec } from '../../../util/exec'; +import type { ExecOptions } from '../../../util/exec/types'; +import { + getParentDir, + getSiblingFileName, + readLocalFile, + writeLocalFile, +} from '../../../util/fs'; +import { getRepoStatus } from '../../../util/git'; +import type { UpdateArtifact, UpdateArtifactsResult } from '../types'; + +export async function updateArtifacts({ + packageFileName, + updatedDeps, + newPackageFileContent, + config, +}: UpdateArtifact): Promise { + logger.debug(`vendir.updateArtifacts(${packageFileName})`); + + const lockFileName = getSiblingFileName(packageFileName, 'vendir.lock.yml'); + if (!lockFileName) { + logger.warn('No vendir.lock.yml found'); + return null; + } + const existingLockFileContent = await readLocalFile(lockFileName, 'utf8'); + if (!existingLockFileContent) { + logger.warn('Empty vendir.lock.yml found'); + return null; + } + + try { + await writeLocalFile(packageFileName, newPackageFileContent); + logger.debug('Updating Vendir artifacts'); + const execOptions: ExecOptions = { + cwdFile: packageFileName, + docker: {}, + toolConstraints: [ + { toolName: 'vendir', constraint: config.constraints?.vendir }, + { toolName: 'helm', constraint: config.constraints?.helm }, + ], + }; + + await exec(`vendir sync`, execOptions); + + logger.debug('Returning updated Vendir artifacts'); + + const fileChanges: UpdateArtifactsResult[] = []; + + const newVendirLockContent = await readLocalFile(lockFileName, 'utf8'); + const isLockFileChanged = existingLockFileContent !== newVendirLockContent; + if (isLockFileChanged) { + fileChanges.push({ + file: { + type: 'addition', + path: lockFileName, + contents: newVendirLockContent, + }, + }); + } + + // add modified vendir archives to artifacts + logger.debug("Adding Sync'd files to git"); + // Files must be in the vendor path to get added + const vendorDir = getParentDir(packageFileName); + const status = await getRepoStatus(); + if (status) { + const modifiedFiles = status.modified ?? []; + const notAddedFiles = status.not_added; + const deletedFiles = status.deleted ?? []; + + for (const f of modifiedFiles.concat(notAddedFiles)) { + const isFileInVendorDir = f.startsWith(vendorDir); + if (vendorDir || isFileInVendorDir) { + fileChanges.push({ + file: { + type: 'addition', + path: f, + contents: await readLocalFile(f), + }, + }); + } + } + + for (const f of deletedFiles) { + fileChanges.push({ + file: { + type: 'deletion', + path: f, + }, + }); + } + } else { + logger.error('Failed to get git status'); + } + + return fileChanges.length ? fileChanges : null; + } catch (err) { + if (err.message === TEMPORARY_ERROR) { + throw err; + } + logger.debug({ err }, 'Failed to update Vendir lock file'); + return [ + { + artifactError: { + lockFile: lockFileName, + stderr: err.message, + }, + }, + ]; + } +} diff --git a/lib/modules/manager/vendir/extract.spec.ts b/lib/modules/manager/vendir/extract.spec.ts new file mode 100644 index 00000000000000..f34a482b734997 --- /dev/null +++ b/lib/modules/manager/vendir/extract.spec.ts @@ -0,0 +1,91 @@ +import { codeBlock } from 'common-tags'; +import { Fixtures } from '../../../../test/fixtures'; +import { extractPackageFile } from '.'; + +const validContents = Fixtures.get('valid-contents.yaml'); +const invalidContents = Fixtures.get('invalid-contents.yaml'); + +describe('modules/manager/vendir/extract', () => { + describe('extractPackageFile()', () => { + it('returns null for invalid yaml file content', () => { + const result = extractPackageFile('nothing here: [', 'vendir.yml', {}); + expect(result).toBeNull(); + }); + + it('returns null for empty yaml file content', () => { + const result = extractPackageFile('', 'vendir.yml', {}); + expect(result).toBeNull(); + }); + + it('returns null for empty directories key', () => { + const emptyDirectories = codeBlock` + apiVersion: vendir.k14s.io/v1alpha1 + kind: Config + directories: [] + `; + const result = extractPackageFile(emptyDirectories, 'vendir.yml', {}); + expect(result).toBeNull(); + }); + + it('returns null for nonHelmChart key', () => { + const result = extractPackageFile(invalidContents, 'vendir.yml', {}); + expect(result).toBeNull(); + }); + + it('multiple charts - extracts helm-chart from vendir.yml correctly', () => { + const result = extractPackageFile(validContents, 'vendir.yml', { + registryAliases: { + test: 'quay.example.com/organization', + }, + }); + expect(result).toMatchObject({ + deps: [ + { + currentValue: '7.10.1', + depName: 'valid-helmchart-1', + datasource: 'helm', + depType: 'HelmChart', + registryUrls: ['https://charts.bitnami.com/bitnami'], + }, + { + currentValue: '7.10.1', + depName: 'valid-helmchart-2', + datasource: 'helm', + depType: 'HelmChart', + registryUrls: ['https://charts.bitnami.com/bitnami'], + }, + { + currentDigest: undefined, + currentValue: '7.10.1', + depName: 'oci-chart', + datasource: 'docker', + depType: 'HelmChart', + packageName: 'charts.bitnami.com/bitnami/oci-chart', + pinDigests: false, + }, + { + currentDigest: undefined, + currentValue: '7.10.1', + depName: 'aliased-oci-chart', + datasource: 'docker', + depType: 'HelmChart', + packageName: 'quay.example.com/organization/aliased-oci-chart', + pinDigests: false, + }, + { + currentValue: '7.10.1', + depName: 'https://github.com/test/test', + packageName: 'https://github.com/test/test', + datasource: 'git-refs', + }, + { + currentValue: '7.10.1', + depName: 'test/test', + packageName: 'test/test', + datasource: 'github-releases', + }, + ], + }); + }); + }); +}); diff --git a/lib/modules/manager/vendir/extract.ts b/lib/modules/manager/vendir/extract.ts new file mode 100644 index 00000000000000..a9e3ee073c9b46 --- /dev/null +++ b/lib/modules/manager/vendir/extract.ts @@ -0,0 +1,130 @@ +import { logger } from '../../../logger'; +import { getHttpUrl } from '../../../util/git/url'; +import { parseSingleYaml } from '../../../util/yaml'; +import { GitRefsDatasource } from '../../datasource/git-refs'; +import { GithubReleasesDatasource } from '../../datasource/github-releases'; +import { HelmDatasource } from '../../datasource/helm'; +import { getDep } from '../dockerfile/extract'; +import { isOCIRegistry, removeOCIPrefix } from '../helmv3/oci'; +import type { + ExtractConfig, + PackageDependency, + PackageFileContent, +} from '../types'; +import { + GitRefDefinition, + GithubReleaseDefinition, + HelmChartDefinition, + Vendir, + VendirDefinition, +} from './schema'; + +export function extractHelmChart( + helmChart: HelmChartDefinition, + aliases?: Record | undefined, +): PackageDependency | null { + if (isOCIRegistry(helmChart.repository.url)) { + const dep = getDep( + `${removeOCIPrefix(helmChart.repository.url)}/${helmChart.name}:${helmChart.version}`, + false, + aliases, + ); + return { + ...dep, + depName: helmChart.name, + packageName: dep.depName, + depType: 'HelmChart', + // https://github.com/helm/helm/issues/10312 + // https://github.com/helm/helm/issues/10678 + pinDigests: false, + }; + } + return { + depName: helmChart.name, + currentValue: helmChart.version, + depType: 'HelmChart', + registryUrls: [helmChart.repository.url], + datasource: HelmDatasource.id, + }; +} + +export function extractGitSource( + gitSource: GitRefDefinition, +): PackageDependency | null { + const httpUrl = getHttpUrl(gitSource.url); + return { + depName: httpUrl, + packageName: httpUrl, + depType: 'GitSource', + currentValue: gitSource.ref, + registryUrls: [httpUrl], + datasource: GitRefsDatasource.id, + }; +} + +export function extractGithubReleaseSource( + githubRelease: GithubReleaseDefinition, +): PackageDependency | null { + return { + depName: githubRelease.slug, + packageName: githubRelease.slug, + depType: 'GithubRelease', + currentValue: githubRelease.tag, + datasource: GithubReleasesDatasource.id, + }; +} + +export function parseVendir( + content: string, + packageFile?: string, +): VendirDefinition | null { + try { + return parseSingleYaml(content, { + customSchema: Vendir, + removeTemplates: true, + }); + } catch (e) { + logger.debug({ packageFile }, 'Error parsing vendir.yml file'); + return null; + } +} + +export function extractPackageFile( + content: string, + packageFile: string, + config: ExtractConfig, +): PackageFileContent | null { + logger.trace(`vendir.extractPackageFile(${packageFile})`); + const deps: PackageDependency[] = []; + + const pkg = parseVendir(content, packageFile); + if (!pkg) { + return null; + } + + // grab the helm charts + const contents = pkg.directories.flatMap((directory) => directory.contents); + for (const content of contents) { + if ('helmChart' in content && content.helmChart) { + const dep = extractHelmChart(content.helmChart, config.registryAliases); + if (dep) { + deps.push(dep); + } + } else if ('git' in content && content.git) { + const dep = extractGitSource(content.git); + if (dep) { + deps.push(dep); + } + } else if ('githubRelease' in content && content.githubRelease) { + const dep = extractGithubReleaseSource(content.githubRelease); + if (dep) { + deps.push(dep); + } + } + } + + if (!deps.length) { + return null; + } + return { deps }; +} diff --git a/lib/modules/manager/vendir/index.ts b/lib/modules/manager/vendir/index.ts new file mode 100644 index 00000000000000..aaab07563e9f87 --- /dev/null +++ b/lib/modules/manager/vendir/index.ts @@ -0,0 +1,12 @@ +import { DockerDatasource } from '../../datasource/docker'; +import { HelmDatasource } from '../../datasource/helm'; +export { extractPackageFile } from './extract'; +export { updateArtifacts } from './artifacts'; + +export const defaultConfig = { + commitMessageTopic: 'vendir {{depName}}', + fileMatch: ['(^|/)vendir\\.yml$'], +}; + +export const supportedDatasources = [HelmDatasource.id, DockerDatasource.id]; +export const supportsLockFileMaintenance = true; diff --git a/lib/modules/manager/vendir/readme.md b/lib/modules/manager/vendir/readme.md new file mode 100644 index 00000000000000..6ab7931a72ba3d --- /dev/null +++ b/lib/modules/manager/vendir/readme.md @@ -0,0 +1,77 @@ +Renovate supports updating Helm Chart references and git references in vendir.yml via the [vendir](https://carvel.dev/vendir/) tool. Renovate requires the presence of a [vendir lock file](https://carvel.dev/vendir/docs/v0.40.x/vendir-lock-spec/) which is generated by vendir and should be stored in source code. + +### Helm Charts + +It supports both https and oci helm chart repositories. + +```yaml title="Example helm chart vendir.yml" +apiVersion: vendir.k14s.io/v1alpha1 +kind: Config + +# one or more directories to manage with vendir +directories: + - # path is relative to `vendir` CLI working directory + path: config/_ytt_lib + contents: + path: github.com/cloudfoundry/cf-k8s-networking + helmChart: + # chart name (required) + name: stable/redis + # use specific chart version (string; optional) + version: '1.2.1' + # specifies Helm repository to fetch from (optional) + repository: + # repository url; supports exprimental oci helm fetch via + # oci:// scheme (required) + url: https://... + # specify helm binary version to use; + # '3' means binary 'helm3' needs to be on the path (optional) + helmVersion: '3' +``` + +### Registry Aliases + +#### OCI + +Aliases for OCI registries are supported via the dockerfile/docker manager + +### Git + +Renovates supporting explicit refs in for git references in vendir.yml + +```yaml title="Example git vendir.yml" +apiVersion: vendir.k14s.io/v1alpha1 +kind: Config + +# one or more directories to manage with vendir +directories: + - path: config/_ytt_lib + contents: + path: github.com/cloudfoundry/cf-k8s-networking + git: + # http or ssh urls are supported (required) + url: https://github.com/cloudfoundry/cf-k8s-networking + # branch, tag, commit; origin is the name of the remote (required) + # optional if refSelection is specified (available in v0.11.0+) + ref: origin/master + # depth of commits to fetch; 0 (default) means everything (optional; v0.29.0+) + depth: 1 + ... +``` + +### GithubRelease + +Renovates supporting explicit tags in for github releases in vendir.yml + +```yaml title="Example github vendir.yml" +directories: + - path: config/_ytt_lib + contents: + path: github.com/cloudfoundry/cf-k8s-networking + githubRelease: + # slug for repository (org/repo) (required) + slug: k14s/kapp-controller + # use release tag (optional) + # optional if tagSelection is specified (available in v0.22.0+) + tag: v0.1.0 +``` diff --git a/lib/modules/manager/vendir/schema.ts b/lib/modules/manager/vendir/schema.ts new file mode 100644 index 00000000000000..93c5a7d99245e7 --- /dev/null +++ b/lib/modules/manager/vendir/schema.ts @@ -0,0 +1,61 @@ +import { z } from 'zod'; +import { LooseArray } from '../../../util/schema-utils'; + +export const VendirResource = z.object({ + apiVersion: z.literal('vendir.k14s.io/v1alpha1'), + kind: z.literal('Config'), +}); + +export const GitRef = z.object({ + ref: z.string(), + url: z.string().regex(/^(?:ssh|https?):\/\/.+/), + depth: z.number().optional(), +}); + +export const GithubRelease = z.object({ + slug: z.string(), + tag: z.string(), +}); + +export const HelmChart = z.object({ + name: z.string(), + version: z.string(), + repository: z.object({ + url: z.string().regex(/^(?:oci|https?):\/\/.+/), + }), +}); + +export const HelmChartContent = z.object({ + path: z.string(), + helmChart: HelmChart, +}); + +export const GitRefContent = z.object({ + path: z.string(), + git: GitRef, +}); + +export const GithubReleaseContent = z.object({ + path: z.string(), + githubRelease: GithubRelease, +}); + +export const Contents = z.union([ + HelmChartContent, + GitRefContent, + GithubReleaseContent, +]); + +export const Vendir = VendirResource.extend({ + directories: z.array( + z.object({ + path: z.string(), + contents: LooseArray(Contents), + }), + ), +}); + +export type VendirDefinition = z.infer; +export type HelmChartDefinition = z.infer; +export type GitRefDefinition = z.infer; +export type GithubReleaseDefinition = z.infer; diff --git a/lib/modules/manager/woodpecker/readme.md b/lib/modules/manager/woodpecker/readme.md index 488d868d53c473..197681ca33ba72 100644 --- a/lib/modules/manager/woodpecker/readme.md +++ b/lib/modules/manager/woodpecker/readme.md @@ -4,4 +4,4 @@ Extracts all Docker images from Woodpecker Pipeline YAML files. - [Woodpecker Docs: Pipeline Syntax](https://woodpecker-ci.org/docs/usage/pipeline-syntax) ([section with dependencies](https://woodpecker-ci.org/docs/usage/pipeline-syntax#image)) - [`woodpecker-ci` JSON schema](https://raw.githubusercontent.com/woodpecker-ci/woodpecker/master/pipeline/schema/schema.json) -If you need to change the versioning format, read the [versioning](../../versioning.md) documentation to learn more. +If you need to change the versioning format, read the [versioning](../../versioning/index.md) documentation to learn more. diff --git a/lib/modules/platform/azure/azure-helper.spec.ts b/lib/modules/platform/azure/azure-helper.spec.ts index 1e7a722ab38f6b..3b9f1db120a739 100644 --- a/lib/modules/platform/azure/azure-helper.spec.ts +++ b/lib/modules/platform/azure/azure-helper.spec.ts @@ -1,5 +1,8 @@ import { Readable } from 'node:stream'; -import { GitPullRequestMergeStrategy } from 'azure-devops-node-api/interfaces/GitInterfaces.js'; +import type { IPolicyApi } from 'azure-devops-node-api/PolicyApi'; +import { GitPullRequestMergeStrategy } from 'azure-devops-node-api/interfaces/GitInterfaces'; +import type { PolicyConfiguration } from 'azure-devops-node-api/interfaces/PolicyInterfaces'; +import { partial } from '../../../../test/util'; jest.mock('./azure-got-wrapper'); @@ -238,6 +241,35 @@ describe('modules/platform/azure/azure-helper', () => { ); }); + it('should return Squash when Project wide exact branch policy exists', async () => { + const refMock = 'refs/heads/ding'; + + azureApi.policyApi.mockResolvedValueOnce( + partial({ + getPolicyConfigurations: jest.fn(() => + Promise.resolve([ + partial({ + settings: { + allowSquash: true, + scope: [ + { + // null here means project wide + repositoryId: null, + matchKind: 'Exact', + refName: refMock, + }, + ], + }, + }), + ]), + ), + }), + ); + expect(await azureHelper.getMergeMethod('', '', refMock)).toEqual( + GitPullRequestMergeStrategy.Squash, + ); + }); + it('should return default branch policy', async () => { azureApi.policyApi.mockImplementationOnce( () => @@ -402,4 +434,27 @@ describe('modules/platform/azure/azure-helper', () => { ).toEqual(GitPullRequestMergeStrategy.Rebase); }); }); + + describe('getAllProjectTeams', () => { + it('should get all teams ', async () => { + const team1 = Array.from({ length: 100 }, (_, index) => ({ + description: `team1 ${index + 1}`, + })); + const team2 = Array.from({ length: 3 }, (_, index) => ({ + description: `team2 ${index + 1}`, + })); + const allTeams = team1.concat(team2); + azureApi.coreApi.mockImplementationOnce( + () => + ({ + getTeams: jest + .fn() + .mockResolvedValueOnce(team1) + .mockResolvedValueOnce(team2), + }) as any, + ); + const res = await azureHelper.getAllProjectTeams('projectId'); + expect(res).toEqual(allTeams); + }); + }); }); diff --git a/lib/modules/platform/azure/azure-helper.ts b/lib/modules/platform/azure/azure-helper.ts index e215c7a7c8f739..3aa9a353971022 100644 --- a/lib/modules/platform/azure/azure-helper.ts +++ b/lib/modules/platform/azure/azure-helper.ts @@ -1,3 +1,4 @@ +import type { WebApiTeam } from 'azure-devops-node-api/interfaces/CoreInterfaces.js'; import { GitCommit, GitPullRequestMergeStrategy, @@ -135,7 +136,7 @@ export async function getMergeMethod( ) { return true; } - if (scope.repositoryId !== repoId) { + if (scope.repositoryId !== repoId && scope.repositoryId !== null) { return false; } if (!branchRef) { @@ -178,3 +179,22 @@ export async function getMergeMethod( return GitPullRequestMergeStrategy.NoFastForward; } } + +export async function getAllProjectTeams( + projectId: string, +): Promise { + const allTeams: WebApiTeam[] = []; + const azureApiCore = await azureApi.coreApi(); + const top = 100; + let skip = 0; + let length = 0; + + do { + const teams = await azureApiCore.getTeams(projectId, undefined, top, skip); + length = teams.length; + allTeams.push(...teams); + skip += top; + } while (top <= length); + + return allTeams; +} diff --git a/lib/modules/platform/azure/index.spec.ts b/lib/modules/platform/azure/index.spec.ts index b39ae81e91b284..934cb93e8c2281 100644 --- a/lib/modules/platform/azure/index.spec.ts +++ b/lib/modules/platform/azure/index.spec.ts @@ -1486,15 +1486,15 @@ describe('modules/platform/azure/index', () => { azureApi.coreApi.mockImplementation( () => ({ - getTeams: jest.fn(() => [ - { id: 3, name: 'abc' }, - { id: 4, name: 'def' }, - ]), getTeamMembersWithExtendedProperties: jest.fn(() => [ { identity: { displayName: 'jyc', uniqueName: 'jyc', id: 123 } }, ]), }) as any, ); + azureHelper.getAllProjectTeams = jest.fn().mockReturnValue([ + { id: 3, name: 'abc' }, + { id: 4, name: 'def' }, + ]); await azure.addAssignees(123, ['test@bonjour.fr', 'jyc', 'def']); expect(azureApi.gitApi).toHaveBeenCalledTimes(3); }); @@ -1513,15 +1513,15 @@ describe('modules/platform/azure/index', () => { azureApi.coreApi.mockImplementation( () => ({ - getTeams: jest.fn(() => [ - { id: 3, name: 'abc' }, - { id: 4, name: 'def' }, - ]), getTeamMembersWithExtendedProperties: jest.fn(() => [ { identity: { displayName: 'jyc', uniqueName: 'jyc', id: 123 } }, ]), }) as any, ); + azureHelper.getAllProjectTeams = jest.fn().mockReturnValue([ + { id: 3, name: 'abc' }, + { id: 4, name: 'def' }, + ]); await azure.addReviewers(123, ['test@bonjour.fr', 'jyc', 'required:def']); expect(azureApi.gitApi).toHaveBeenCalledTimes(3); expect(logger.once.info).toHaveBeenCalledTimes(1); @@ -1539,15 +1539,15 @@ describe('modules/platform/azure/index', () => { azureApi.coreApi.mockImplementation( () => ({ - getTeams: jest.fn(() => [ - { id: 3, name: 'abc' }, - { id: 4, name: 'def' }, - ]), getTeamMembersWithExtendedProperties: jest.fn(() => [ { identity: { displayName: 'jyc', uniqueName: 'jyc', id: 123 } }, ]), }) as any, ); + azureHelper.getAllProjectTeams = jest.fn().mockReturnValue([ + { id: 3, name: 'abc' }, + { id: 4, name: 'def' }, + ]); await azure.addReviewers(123, ['required:jyc']); expect(azureApi.gitApi).toHaveBeenCalledTimes(3); expect(logger.once.info).toHaveBeenCalledTimes(0); diff --git a/lib/modules/platform/azure/index.ts b/lib/modules/platform/azure/index.ts index 7dc3e1357564b2..773a7e26227247 100644 --- a/lib/modules/platform/azure/index.ts +++ b/lib/modules/platform/azure/index.ts @@ -383,6 +383,7 @@ const azureToRenovateStatusMapping: Record = { [GitStatusState.NotApplicable]: 'green', [GitStatusState.NotSet]: 'yellow', [GitStatusState.Pending]: 'yellow', + [GitStatusState.PartiallySucceeded]: 'yellow', [GitStatusState.Error]: 'red', [GitStatusState.Failed]: 'red', }; @@ -854,7 +855,7 @@ async function getUserIds(users: string[]): Promise { const validReviewers = new Set(); // TODO #22198 - const teams = await azureApiCore.getTeams(repo.project!.id!); + const teams = await azureHelper.getAllProjectTeams(repo.project!.id!); const members = await Promise.all( teams.map( async (t) => diff --git a/lib/modules/platform/bitbucket-server/index.spec.ts b/lib/modules/platform/bitbucket-server/index.spec.ts index feb59252d1fa8b..b980ab9f61c2ab 100644 --- a/lib/modules/platform/bitbucket-server/index.spec.ts +++ b/lib/modules/platform/bitbucket-server/index.spec.ts @@ -1,11 +1,13 @@ import is from '@sindresorhus/is'; import { mockDeep } from 'jest-mock-extended'; import * as httpMock from '../../../../test/http-mock'; +import { mocked } from '../../../../test/util'; import { REPOSITORY_CHANGED, REPOSITORY_EMPTY, REPOSITORY_NOT_FOUND, } from '../../../constants/error-messages'; +import type { logger as _logger } from '../../../logger'; import type * as _git from '../../../util/git'; import type { LongCommitSha } from '../../../util/git/types'; import type { Platform } from '../types'; @@ -185,6 +187,7 @@ describe('modules/platform/bitbucket-server/index', () => { let hostRules: jest.Mocked; let git: jest.Mocked; + let logger: jest.Mocked; const username = 'abc'; const password = '123'; @@ -211,6 +214,7 @@ describe('modules/platform/bitbucket-server/index', () => { // reset module jest.resetModules(); bitbucket = await import('.'); + logger = mocked(await import('../../../logger')).logger; hostRules = jest.requireMock('../../../util/host-rules'); git = jest.requireMock('../../../util/git'); git.branchExists.mockReturnValue(true); @@ -226,6 +230,10 @@ describe('modules/platform/bitbucket-server/index', () => { username, password, }); + httpMock + .scope(urlHost) + .get(`${urlPath}/rest/api/1.0/application-properties`) + .reply(200, { version: '8.0.0' }); await bitbucket.initPlatform({ endpoint, username, @@ -234,19 +242,85 @@ describe('modules/platform/bitbucket-server/index', () => { }); describe('initPlatform()', () => { - it('should throw if no endpoint', () => { + it('should throw if no endpoint', async () => { expect.assertions(1); - expect(() => bitbucket.initPlatform({})).toThrow(); + await expect(bitbucket.initPlatform({})).rejects.toThrow(); }); - it('should throw if no username/password', () => { + it('should throw if no username/password/token', async () => { expect.assertions(1); - expect(() => + await expect( bitbucket.initPlatform({ endpoint: 'endpoint' }), - ).toThrow(); + ).rejects.toThrow(); + }); + + it('should throw if password and token is set', async () => { + expect.assertions(1); + await expect( + bitbucket.initPlatform({ + endpoint: 'endpoint', + username: 'abc', + password: '123', + token: 'abc', + }), + ).rejects.toThrow(); + }); + + it('should not throw if username/password', async () => { + expect.assertions(1); + await expect( + bitbucket.initPlatform({ + endpoint: 'endpoint', + username: 'abc', + password: '123', + }), + ).resolves.not.toThrow(); + }); + + it('should not throw if token', async () => { + expect.assertions(1); + await expect( + bitbucket.initPlatform({ + endpoint: 'endpoint', + token: 'abc', + }), + ).resolves.not.toThrow(); + }); + + it('should throw if version could not be fetched', async () => { + httpMock + .scope('https://stash.renovatebot.com') + .get('/rest/api/1.0/application-properties') + .reply(403); + + await bitbucket.initPlatform({ + endpoint: 'https://stash.renovatebot.com', + username: 'abc', + password: '123', + }); + expect(logger.debug).toHaveBeenCalledWith( + expect.any(Object), + 'Error authenticating with Bitbucket. Check that your token includes "api" permissions', + ); + }); + + it('should skip api call to fetch version when platform version is set in environment', async () => { + process.env.RENOVATE_X_PLATFORM_VERSION = '8.0.0'; + await expect( + bitbucket.initPlatform({ + endpoint: 'https://stash.renovatebot.com', + username: 'abc', + password: '123', + }), + ).toResolve(); + delete process.env.RENOVATE_X_PLATFORM_VERSION; }); it('should init', async () => { + httpMock + .scope('https://stash.renovatebot.com') + .get('/rest/api/1.0/application-properties') + .reply(200, { version: '8.0.0' }); expect( await bitbucket.initPlatform({ endpoint: 'https://stash.renovatebot.com', diff --git a/lib/modules/platform/bitbucket-server/index.ts b/lib/modules/platform/bitbucket-server/index.ts index 3b87a94a5916fc..1d0fb5f6078952 100644 --- a/lib/modules/platform/bitbucket-server/index.ts +++ b/lib/modules/platform/bitbucket-server/index.ts @@ -1,4 +1,5 @@ import { setTimeout } from 'timers/promises'; +import semver from 'semver'; import type { PartialDeep } from 'type-fest'; import { REPOSITORY_CHANGED, @@ -48,6 +49,7 @@ import type { BbsRestUserRef, } from './types'; import * as utils from './utils'; +import { getExtraCloneOpts } from './utils'; /* * Version: 5.3 (EOL Date: 15 Aug 2019) @@ -68,8 +70,10 @@ const bitbucketServerHttp = new BitbucketServerHttp(); const defaults: { endpoint?: string; hostType: string; + version: string; } = { hostType: 'bitbucket-server', + version: '0.0.0', }; /* istanbul ignore next */ @@ -79,17 +83,22 @@ function updatePrVersion(pr: number, version: number): number { return res; } -export function initPlatform({ +export async function initPlatform({ endpoint, + token, username, password, }: PlatformParams): Promise { if (!endpoint) { throw new Error('Init: You must configure a Bitbucket Server endpoint'); } - if (!(username && password)) { + if (!(username && password) && !token) { throw new Error( - 'Init: You must configure a Bitbucket Server username/password', + 'Init: You must either configure a Bitbucket Server username/password or a HTTP access token', + ); + } else if (password && token) { + throw new Error( + 'Init: You must either configure a Bitbucket Server password or a HTTP access token', ); } // TODO: Add a connection check that endpoint/username/password combination are valid (#9595) @@ -98,7 +107,32 @@ export function initPlatform({ const platformConfig: PlatformResult = { endpoint: defaults.endpoint, }; - return Promise.resolve(platformConfig); + try { + let bitbucketServerVersion: string; + // istanbul ignore if: experimental feature + if (process.env.RENOVATE_X_PLATFORM_VERSION) { + bitbucketServerVersion = process.env.RENOVATE_X_PLATFORM_VERSION; + } else { + const { version } = ( + await bitbucketServerHttp.getJson<{ version: string }>( + `./rest/api/1.0/application-properties`, + ) + ).body; + bitbucketServerVersion = version; + logger.debug('Bitbucket Server version is: ' + bitbucketServerVersion); + } + + if (semver.valid(bitbucketServerVersion)) { + defaults.version = bitbucketServerVersion; + } + } catch (err) { + logger.debug( + { err }, + 'Error authenticating with Bitbucket. Check that your token includes "api" permissions', + ); + } + + return platformConfig; } // Get all repositories that the user has access to @@ -203,8 +237,9 @@ export async function initRepo({ await git.initRepo({ ...config, url, + extraCloneOpts: getExtraCloneOpts(opts), cloneSubmodules, - fullClone: true, + fullClone: semver.lte(defaults.version, '8.0.0'), }); config.mergeMethod = 'merge'; @@ -300,7 +335,7 @@ export async function getPrList(refreshCache?: boolean): Promise { const searchParams: Record = { state: 'ALL', }; - if (!config.ignorePrAuthor) { + if (!config.ignorePrAuthor && config.username !== undefined) { searchParams['role.1'] = 'AUTHOR'; searchParams['username.1'] = config.username; } diff --git a/lib/modules/platform/bitbucket-server/readme.md b/lib/modules/platform/bitbucket-server/readme.md index 1eae83c077993a..722c0f0defe4a5 100644 --- a/lib/modules/platform/bitbucket-server/readme.md +++ b/lib/modules/platform/bitbucket-server/readme.md @@ -5,9 +5,11 @@ First, create a [HTTP access token](https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html) for the bot account. Let Renovate use your HTTP access token by doing _one_ of the following: -- Set your HTTP access token as a `password` in your `config.js` file -- Set your HTTP access token as an environment variable `RENOVATE_PASSWORD` -- Set your HTTP access token when you run Renovate in the CLI with `--password=` +- Set your HTTP access token as a `token` in your `config.js` file +- Set your HTTP access token as an environment variable `RENOVATE_TOKEN` +- Set your HTTP access token when you run Renovate in the CLI with `--token=` + +If you use project or repository based HTTP access tokens, it can only be used as `token`. Remember to set `platform=bitbucket-server` somewhere in your Renovate config file. diff --git a/lib/modules/platform/bitbucket-server/utils.spec.ts b/lib/modules/platform/bitbucket-server/utils.spec.ts index 24c6c54cbb7228..9625459ad310e3 100644 --- a/lib/modules/platform/bitbucket-server/utils.spec.ts +++ b/lib/modules/platform/bitbucket-server/utils.spec.ts @@ -8,6 +8,7 @@ import type { } from './types'; import { BITBUCKET_INVALID_REVIEWERS_EXCEPTION, + getExtraCloneOpts, getInvalidReviewers, getRepoGitUrl, } from './utils'; @@ -268,6 +269,32 @@ describe('modules/platform/bitbucket-server/utils', () => { ), ); }); + + it('works gitUrl:endpoint no basic auth', () => { + expect( + getRepoGitUrl( + 'SOME/repo', + url.toString(), + 'endpoint', + infoMock(url, 'SOME', 'repo'), + {}, + ), + ).toBe(httpLink(url.toString(), 'SOME', 'repo')); + }); + }); + }); + }); + + describe('getExtraCloneOpts', () => { + it('should not configure bearer token', () => { + const res = getExtraCloneOpts({}); + expect(res).toEqual({}); + }); + + it('should configure bearer token', () => { + const res = getExtraCloneOpts({ token: 'abc' }); + expect(res).toEqual({ + '-c': 'http.extraheader=Authorization: Bearer abc', }); }); }); diff --git a/lib/modules/platform/bitbucket-server/utils.ts b/lib/modules/platform/bitbucket-server/utils.ts index 882451953cbc90..92099dafab02b6 100644 --- a/lib/modules/platform/bitbucket-server/utils.ts +++ b/lib/modules/platform/bitbucket-server/utils.ts @@ -4,7 +4,7 @@ import is from '@sindresorhus/is'; import { CONFIG_GIT_URL_UNAVAILABLE } from '../../../constants/error-messages'; import { logger } from '../../../logger'; import type { HostRule } from '../../../types'; -import type { GitProtocol } from '../../../types/git'; +import type { GitOptions, GitProtocol } from '../../../types/git'; import * as git from '../../../util/git'; import { BitbucketServerHttp } from '../../../util/http/bitbucket-server'; import type { HttpOptions, HttpResponse } from '../../../util/http/types'; @@ -174,9 +174,10 @@ function injectAuth(url: string, opts: HostRule): string { logger.debug(`Invalid url: ${url}`); throw new Error(CONFIG_GIT_URL_UNAVAILABLE); } - // TODO: null checks (#22198) - repoUrl.username = opts.username!; - repoUrl.password = opts.password!; + if (!opts.token && opts.username && opts.password) { + repoUrl.username = opts.username; + repoUrl.password = opts.password; + } return repoUrl.toString(); } @@ -208,3 +209,12 @@ export function getRepoGitUrl( // SSH urls can be used directly return cloneUrl.href; } + +export function getExtraCloneOpts(opts: HostRule): GitOptions { + if (opts.token) { + return { + '-c': `http.extraheader=Authorization: Bearer ${opts.token}`, + }; + } + return {}; +} diff --git a/lib/modules/platform/bitbucket/__snapshots__/index.spec.ts.snap b/lib/modules/platform/bitbucket/__snapshots__/index.spec.ts.snap index 1818c5b6361ede..b009eb1bde00d7 100644 --- a/lib/modules/platform/bitbucket/__snapshots__/index.spec.ts.snap +++ b/lib/modules/platform/bitbucket/__snapshots__/index.spec.ts.snap @@ -132,14 +132,6 @@ exports[`modules/platform/bitbucket/index getPrList() filters PR list by author ] `; -exports[`modules/platform/bitbucket/index initRepo() works with username and password 1`] = ` -{ - "defaultBranch": "master", - "isFork": false, - "repoFingerprint": "56653db0e9341ef4957c92bb78ee668b0a3f03c75b77db94d520230557385fca344cc1f593191e3594183b5b050909d29996c040045e8852f21774617b240642", -} -`; - exports[`modules/platform/bitbucket/index massageMarkdown() returns diff files 1`] = ` "**foo** diff --git a/lib/modules/platform/bitbucket/index.spec.ts b/lib/modules/platform/bitbucket/index.spec.ts index e958ea7c07d339..66fd23ac131a2e 100644 --- a/lib/modules/platform/bitbucket/index.spec.ts +++ b/lib/modules/platform/bitbucket/index.spec.ts @@ -56,8 +56,9 @@ describe('modules/platform/bitbucket/index', () => { const scope = existingScope ?? httpMock.scope(baseUrl); scope.get(`/2.0/repositories/${repository}`).reply(200, { - owner: {}, mainbranch: { name: 'master' }, + uuid: '123', + full_name: 'some/repo', ...repoResp, }); @@ -131,11 +132,70 @@ describe('modules/platform/bitbucket/index', () => { .scope(baseUrl) .get('/2.0/repositories?role=contributor&pagelen=100') .reply(200, { - values: [{ full_name: 'foo/bar' }, { full_name: 'some/repo' }], + values: [ + { + mainbranch: { name: 'master' }, + uuid: '111', + full_name: 'foo/bar', + }, + { + mainbranch: { name: 'master' }, + uuid: '222', + full_name: 'some/repo', + }, + ], }); - const res = await bitbucket.getRepos(); + const res = await bitbucket.getRepos({}); expect(res).toEqual(['foo/bar', 'some/repo']); }); + + it('filters repos based on autodiscoverProjects patterns', async () => { + httpMock + .scope(baseUrl) + .get('/2.0/repositories?role=contributor&pagelen=100') + .reply(200, { + values: [ + { + mainbranch: { name: 'master' }, + uuid: '111', + full_name: 'foo/bar', + project: { name: 'ignore' }, + }, + { + mainbranch: { name: 'master' }, + uuid: '222', + full_name: 'some/repo', + project: { name: 'allow' }, + }, + ], + }); + const res = await bitbucket.getRepos({ projects: ['allow'] }); + expect(res).toEqual(['some/repo']); + }); + + it('filters repos based on autodiscoverProjects patterns with negation', async () => { + httpMock + .scope(baseUrl) + .get('/2.0/repositories?role=contributor&pagelen=100') + .reply(200, { + values: [ + { + mainbranch: { name: 'master' }, + uuid: '111', + full_name: 'foo/bar', + project: { name: 'ignore' }, + }, + { + mainbranch: { name: 'master' }, + uuid: '222', + full_name: 'some/repo', + project: { name: 'allow' }, + }, + ], + }); + const res = await bitbucket.getRepos({ projects: ['!ignore'] }); + expect(res).toEqual(['some/repo']); + }); }); describe('initRepo()', () => { @@ -143,12 +203,20 @@ describe('modules/platform/bitbucket/index', () => { httpMock .scope(baseUrl) .get('/2.0/repositories/some/repo') - .reply(200, { owner: {}, mainbranch: { name: 'master' } }); + .reply(200, { + mainbranch: { name: 'master' }, + uuid: '123', + full_name: 'some/repo', + }); expect( await bitbucket.initRepo({ repository: 'some/repo', }), - ).toMatchSnapshot(); + ).toMatchObject({ + defaultBranch: 'master', + isFork: false, + repoFingerprint: expect.any(String), + }); }); it('works with only token', async () => { @@ -159,16 +227,19 @@ describe('modules/platform/bitbucket/index', () => { httpMock .scope(baseUrl) .get('/2.0/repositories/some/repo') - .reply(200, { owner: {}, mainbranch: { name: 'master' } }); + .reply(200, { + mainbranch: { name: 'master' }, + uuid: '123', + full_name: 'some/repo', + }); expect( await bitbucket.initRepo({ repository: 'some/repo', }), - ).toEqual({ + ).toMatchObject({ defaultBranch: 'master', isFork: false, - repoFingerprint: - '56653db0e9341ef4957c92bb78ee668b0a3f03c75b77db94d520230557385fca344cc1f593191e3594183b5b050909d29996c040045e8852f21774617b240642', + repoFingerprint: expect.any(String), }); }); }); @@ -178,7 +249,11 @@ describe('modules/platform/bitbucket/index', () => { httpMock .scope(baseUrl) .get('/2.0/repositories/some/repo') - .reply(200, { owner: {}, mainbranch: { name: 'master' } }); + .reply(200, { + mainbranch: { name: 'master' }, + uuid: '123', + full_name: 'some/repo', + }); const res = await bitbucket.initRepo({ repository: 'some/repo', @@ -192,7 +267,11 @@ describe('modules/platform/bitbucket/index', () => { httpMock .scope(baseUrl) .get('/2.0/repositories/some/repo') - .reply(200, { owner: {}, mainbranch: { name: 'master' } }) + .reply(200, { + mainbranch: { name: 'master' }, + uuid: '123', + full_name: 'some/repo', + }) .get('/2.0/repositories/some/repo/branching-model') .reply(200, { development: { name: 'develop', branch: { name: 'develop' } }, @@ -210,7 +289,11 @@ describe('modules/platform/bitbucket/index', () => { httpMock .scope(baseUrl) .get('/2.0/repositories/some/repo') - .reply(200, { owner: {}, mainbranch: { name: 'master' } }) + .reply(200, { + mainbranch: { name: 'master' }, + uuid: '123', + full_name: 'some/repo', + }) .get('/2.0/repositories/some/repo/branching-model') .reply(200, { development: { name: 'develop' }, @@ -579,8 +662,8 @@ describe('modules/platform/bitbucket/index', () => { }); describe('ensureIssueClosing()', () => { - it('does not throw', async () => { - await initRepoMock(); + it('does not throw for disabled issues', async () => { + await initRepoMock({ repository: 'some/repo' }, { has_issues: false }); await expect(bitbucket.ensureIssueClosing('title')).toResolve(); }); @@ -613,8 +696,8 @@ describe('modules/platform/bitbucket/index', () => { }); describe('getIssueList()', () => { - it('has no issues', async () => { - await initRepoMock(); + it('returns empty array for disabled issues', async () => { + await initRepoMock({ repository: 'some/repo' }, { has_issues: false }); expect(await bitbucket.getIssueList()).toEqual([]); }); diff --git a/lib/modules/platform/bitbucket/index.ts b/lib/modules/platform/bitbucket/index.ts index dc6301ae89d5a1..bbd7bf997957a2 100644 --- a/lib/modules/platform/bitbucket/index.ts +++ b/lib/modules/platform/bitbucket/index.ts @@ -7,11 +7,13 @@ import { parseJson } from '../../../util/common'; import * as git from '../../../util/git'; import * as hostRules from '../../../util/host-rules'; import { BitbucketHttp, setBaseUrl } from '../../../util/http/bitbucket'; +import { repoCacheProvider } from '../../../util/http/cache/repository-http-cache-provider'; import type { HttpOptions } from '../../../util/http/types'; import { regEx } from '../../../util/regex'; import { sanitize } from '../../../util/sanitize'; -import { UUIDRegex } from '../../../util/string-match'; +import { UUIDRegex, matchRegexOrGlobList } from '../../../util/string-match'; import type { + AutodiscoverConfig, BranchStatusConfig, CreatePRConfig, EnsureCommentConfig, @@ -33,6 +35,7 @@ import { smartTruncate } from '../utils/pr-body'; import { readOnlyIssueBody } from '../utils/read-only-issue-body'; import * as comments from './comments'; import { BitbucketPrCache } from './pr-cache'; +import { RepoInfo, Repositories } from './schema'; import type { Account, BitbucketStatus, @@ -42,8 +45,6 @@ import type { PagedResult, PrResponse, RepoBranchingModel, - RepoInfo, - RepoInfoBody, } from './types'; import * as utils from './utils'; import { mergeBodyTransformer } from './utils'; @@ -113,18 +114,31 @@ export async function initPlatform({ } // Get all repositories that the user has access to -export async function getRepos(): Promise { +export async function getRepos(config: AutodiscoverConfig): Promise { logger.debug('Autodiscovering Bitbucket Cloud repositories'); try { - const repos = ( - await bitbucketHttp.getJson>( - `/2.0/repositories/?role=contributor`, - { - paginate: true, - }, - ) - ).body.values; - return repos.map((repo) => repo.full_name); + let { body: repos } = await bitbucketHttp.getJson( + `/2.0/repositories/?role=contributor`, + { paginate: true }, + Repositories, + ); + + // if autodiscoverProjects is configured + // filter the repos list + const autodiscoverProjects = config.projects; + if (is.nonEmptyArray(autodiscoverProjects)) { + logger.debug( + { autodiscoverProjects: config.projects }, + 'Applying autodiscoverProjects filter', + ); + repos = repos.filter( + (repo) => + repo.projectName && + matchRegexOrGlobList(repo.projectName, autodiscoverProjects), + ); + } + + return repos.map(({ owner, name }) => `${owner}/${name}`); } catch (err) /* istanbul ignore next */ { logger.error({ err }, `bitbucket getRepos error`); throw err; @@ -150,7 +164,10 @@ export async function getRawFile( `/2.0/repositories/${repo}/src/` + (finalBranchOrTag ?? `HEAD`) + `/${path}`; - const res = await bitbucketHttp.get(url); + const res = await bitbucketHttp.get(url, { + cacheProvider: repoCacheProvider, + memCache: true, + }); return res.body; } @@ -183,13 +200,11 @@ export async function initRepo({ let info: RepoInfo; let mainBranch: string; try { - info = utils.repoInfoTransformer( - ( - await bitbucketHttp.getJson( - `/2.0/repositories/${repository}`, - ) - ).body, + const { body: repoInfo } = await bitbucketHttp.getJson( + `/2.0/repositories/${repository}`, + RepoInfo, ); + info = repoInfo; mainBranch = info.mainbranch; @@ -655,13 +670,11 @@ export async function getIssueList(): Promise { filters.push(`reporter.uuid="${renovateUserUuid}"`); } const filter = encodeURIComponent(filters.join(' AND ')); - return ( - ( - await bitbucketHttp.getJson<{ values: Issue[] }>( - `/2.0/repositories/${config.repository}/issues?q=${filter}`, - ) - ).body.values || [] - ); + const url = `/2.0/repositories/${config.repository}/issues?q=${filter}`; + const res = await bitbucketHttp.getJson<{ values: Issue[] }>(url, { + cacheProvider: repoCacheProvider, + }); + return res.body.values || []; } catch (err) { logger.warn({ err }, 'Error finding issues'); return []; @@ -769,7 +782,10 @@ async function sanitizeReviewers( // Validate that each previous PR reviewer account is still active for (const reviewer of reviewers) { const reviewerUser = ( - await bitbucketHttp.getJson(`/2.0/users/${reviewer.uuid}`) + await bitbucketHttp.getJson( + `/2.0/users/${reviewer.uuid}`, + { memCache: true }, + ) ).body; if (reviewerUser.account_status === 'active') { @@ -823,6 +839,7 @@ async function isAccountMemberOfWorkspace( try { await bitbucketHttp.get( `/2.0/workspaces/${workspace}/members/${reviewer.uuid}`, + { memCache: true }, ); return true; diff --git a/lib/modules/platform/bitbucket/pr-cache.spec.ts b/lib/modules/platform/bitbucket/pr-cache.spec.ts index 2dea9fe977b49a..ef71374fb292c4 100644 --- a/lib/modules/platform/bitbucket/pr-cache.spec.ts +++ b/lib/modules/platform/bitbucket/pr-cache.spec.ts @@ -74,6 +74,7 @@ describe('modules/platform/bitbucket/pr-cache', () => { }, ]); expect(cache).toEqual({ + httpCache: {}, platform: { bitbucket: { pullRequestsCache: { @@ -120,6 +121,7 @@ describe('modules/platform/bitbucket/pr-cache', () => { { number: 2, title: 'title' }, ]); expect(cache).toEqual({ + httpCache: {}, platform: { bitbucket: { pullRequestsCache: { diff --git a/lib/modules/platform/bitbucket/pr-cache.ts b/lib/modules/platform/bitbucket/pr-cache.ts index 95fb4b947a7af0..06b9b4f33fc078 100644 --- a/lib/modules/platform/bitbucket/pr-cache.ts +++ b/lib/modules/platform/bitbucket/pr-cache.ts @@ -4,6 +4,7 @@ import { logger } from '../../../logger'; import * as memCache from '../../../util/cache/memory'; import { getCache } from '../../../util/cache/repository'; import type { BitbucketHttp } from '../../../util/http/bitbucket'; +import { repoCacheProvider } from '../../../util/http/cache/repository-http-cache-provider'; import type { Pr } from '../types'; import type { BitbucketPrCacheData, PagedResult, PrResponse } from './types'; import { prFieldsFilter, prInfo, prStates } from './utils'; @@ -19,8 +20,9 @@ export class BitbucketPrCache { repoCache.platform ??= {}; repoCache.platform.bitbucket ??= {}; - let pullRequestCache: BitbucketPrCacheData | undefined = - repoCache.platform.bitbucket.pullRequestsCache; + let pullRequestCache = repoCache.platform.bitbucket.pullRequestsCache as + | BitbucketPrCacheData + | undefined; if (!pullRequestCache || pullRequestCache.author !== author) { pullRequestCache = { items: {}, @@ -127,7 +129,11 @@ export class BitbucketPrCache { private async sync(http: BitbucketHttp): Promise { logger.debug('Syncing PR list'); const url = this.getUrl(); - const opts = { paginate: true, pagelen: 50 }; + const opts = { + paginate: true, + pagelen: 50, + cacheProvider: repoCacheProvider, + }; const res = await http.getJson>(url, opts); this.reconcile(res.body.values); return this; diff --git a/lib/modules/platform/bitbucket/schema.ts b/lib/modules/platform/bitbucket/schema.ts index 48104f2dc55e90..679073591b6f25 100644 --- a/lib/modules/platform/bitbucket/schema.ts +++ b/lib/modules/platform/bitbucket/schema.ts @@ -1,4 +1,6 @@ import { z } from 'zod'; +import { logger } from '../../../logger'; +import { LooseArray } from '../../../util/schema-utils'; const BitbucketSourceTypeSchema = z.enum(['commit_directory', 'commit_file']); @@ -20,3 +22,55 @@ const PagedSchema = z.object({ export const PagedSourceResultsSchema = PagedSchema.extend({ values: z.array(SourceResultsSchema), }); + +export const RepoInfo = z + .object({ + parent: z.unknown().optional().catch(undefined), + mainbranch: z.object({ + name: z.string(), + }), + has_issues: z.boolean().catch(() => { + logger.once.warn('Bitbucket: "has_issues" field missing from repo info'); + return false; + }), + uuid: z.string(), + full_name: z + .string() + .regex( + /^[^/]+\/[^/]+$/, + 'Expected repository full_name to be in the format "owner/repo"', + ), + is_private: z.boolean().catch(() => { + logger.once.warn('Bitbucket: "is_private" field missing from repo info'); + return true; + }), + project: z + .object({ + name: z.string(), + }) + .nullable() + .catch(null), + }) + .transform((repoInfoBody) => { + const isFork = !!repoInfoBody.parent; + const [owner, name] = repoInfoBody.full_name.split('/'); + + return { + isFork, + owner, + name, + mainbranch: repoInfoBody.mainbranch.name, + mergeMethod: 'merge', + has_issues: repoInfoBody.has_issues, + uuid: repoInfoBody.uuid, + is_private: repoInfoBody.is_private, + projectName: repoInfoBody.project?.name, + }; + }); +export type RepoInfo = z.infer; + +export const Repositories = z + .object({ + values: LooseArray(RepoInfo), + }) + .transform((body) => body.values); diff --git a/lib/modules/platform/bitbucket/types.ts b/lib/modules/platform/bitbucket/types.ts index 35fba3d1638a5e..26ef43e3125b53 100644 --- a/lib/modules/platform/bitbucket/types.ts +++ b/lib/modules/platform/bitbucket/types.ts @@ -26,16 +26,6 @@ export interface PagedResult { values: T[]; } -export interface RepoInfo { - isFork: boolean; - owner: string; - mainbranch: string; - mergeMethod: string; - has_issues: boolean; - uuid: string; - is_private: boolean; -} - export interface RepoBranchingModel { development: { name: string; @@ -58,16 +48,6 @@ export interface BitbucketStatus { state: BitbucketBranchState; } -export interface RepoInfoBody { - parent?: any; - owner: { username: string }; - mainbranch: { name: string }; - has_issues: boolean; - uuid: string; - full_name: string; - is_private: boolean; -} - export interface PrResponse { id: number; title: string; diff --git a/lib/modules/platform/bitbucket/utils.ts b/lib/modules/platform/bitbucket/utils.ts index 852f61611164f5..f0d2fa7f9a9676 100644 --- a/lib/modules/platform/bitbucket/utils.ts +++ b/lib/modules/platform/bitbucket/utils.ts @@ -7,22 +7,8 @@ import type { BitbucketMergeStrategy, MergeRequestBody, PrResponse, - RepoInfo, - RepoInfoBody, } from './types'; -export function repoInfoTransformer(repoInfoBody: RepoInfoBody): RepoInfo { - return { - isFork: !!repoInfoBody.parent, - owner: repoInfoBody.owner.username, - mainbranch: repoInfoBody.mainbranch.name, - mergeMethod: 'merge', - has_issues: repoInfoBody.has_issues, - uuid: repoInfoBody.uuid, - is_private: repoInfoBody.is_private, - }; -} - const bitbucketMergeStrategies: Map = new Map([ ['squash', 'squash'], diff --git a/lib/modules/platform/codecommit/index.spec.ts b/lib/modules/platform/codecommit/index.spec.ts index d0ba7ef5512f3a..487e09f12b278f 100644 --- a/lib/modules/platform/codecommit/index.spec.ts +++ b/lib/modules/platform/codecommit/index.spec.ts @@ -2,10 +2,12 @@ import { CodeCommitClient, CreatePullRequestApprovalRuleCommand, CreatePullRequestCommand, + type CreatePullRequestOutput, DeleteCommentContentCommand, GetCommentsForPullRequestCommand, GetFileCommand, GetPullRequestCommand, + type GetPullRequestOutput, GetRepositoryCommand, ListPullRequestsCommand, ListRepositoriesCommand, @@ -257,7 +259,7 @@ describe('modules/platform/codecommit/index', () => { codeCommitClient .on(ListPullRequestsCommand) .resolvesOnce({ pullRequestIds: ['1', '2'] }); - const prRes = { + const prRes: GetPullRequestOutput = { pullRequest: { title: 'someTitle', pullRequestStatus: 'OPEN', @@ -321,7 +323,7 @@ describe('modules/platform/codecommit/index', () => { codeCommitClient .on(ListPullRequestsCommand) .resolvesOnce({ pullRequestIds: ['1'] }); - const prRes = { + const prRes: GetPullRequestOutput = { pullRequest: { title: 'someTitle', pullRequestStatus: 'OPEN', @@ -352,7 +354,7 @@ describe('modules/platform/codecommit/index', () => { codeCommitClient .on(ListPullRequestsCommand) .resolvesOnce({ pullRequestIds: ['1'] }); - const prRes = { + const prRes: GetPullRequestOutput = { pullRequest: { title: 'someTitle', pullRequestStatus: 'OPEN', @@ -383,10 +385,10 @@ describe('modules/platform/codecommit/index', () => { codeCommitClient .on(ListPullRequestsCommand) .resolvesOnce({ pullRequestIds: ['1'] }); - const prRes = { + const prRes: GetPullRequestOutput = { pullRequest: { title: 'someTitle', - pullRequestStatus: '!open', + pullRequestStatus: 'CLOSED', pullRequestTargets: [ { sourceReference: 'refs/heads/sourceBranch', @@ -414,10 +416,10 @@ describe('modules/platform/codecommit/index', () => { codeCommitClient .on(ListPullRequestsCommand) .resolvesOnce({ pullRequestIds: ['1'] }); - const prRes = { + const prRes: GetPullRequestOutput = { pullRequest: { title: 'someTitle', - pullRequestStatus: 'closed', + pullRequestStatus: 'CLOSED', pullRequestTargets: [ { sourceReference: 'refs/heads/sourceBranch', @@ -455,7 +457,7 @@ describe('modules/platform/codecommit/index', () => { codeCommitClient .on(ListPullRequestsCommand) .resolvesOnce({ pullRequestIds: ['1'] }); - const prRes = { + const prRes: GetPullRequestOutput = { pullRequest: { title: 'someTitle', pullRequestStatus: 'OPEN', @@ -482,7 +484,7 @@ describe('modules/platform/codecommit/index', () => { codeCommitClient .on(ListPullRequestsCommand) .resolvesOnce({ pullRequestIds: ['1'] }); - const prRes = { + const prRes: GetPullRequestOutput = { pullRequest: { title: 'someTitle', pullRequestStatus: 'OPEN', @@ -502,7 +504,7 @@ describe('modules/platform/codecommit/index', () => { describe('getPr()', () => { it('gets pr', async () => { - const prRes = { + const prRes: GetPullRequestOutput = { pullRequest: { title: 'someTitle', description: 'body', @@ -529,7 +531,7 @@ describe('modules/platform/codecommit/index', () => { }); it('gets closed pr', async () => { - const prRes = { + const prRes: GetPullRequestOutput = { pullRequest: { title: 'someTitle', pullRequestStatus: 'CLOSED', @@ -555,7 +557,7 @@ describe('modules/platform/codecommit/index', () => { }); it('gets merged pr', async () => { - const prRes = { + const prRes: GetPullRequestOutput = { pullRequest: { title: 'someTitle', pullRequestStatus: 'OPEN', @@ -669,7 +671,7 @@ describe('modules/platform/codecommit/index', () => { describe('createPr()', () => { it('posts PR', async () => { - const prRes = { + const prRes: CreatePullRequestOutput = { pullRequest: { pullRequestId: '1', pullRequestStatus: 'OPEN', @@ -704,7 +706,7 @@ describe('modules/platform/codecommit/index', () => { }); it('doesnt return a title', async () => { - const prRes = { + const prRes: CreatePullRequestOutput = { pullRequest: { pullRequestId: '1', pullRequestStatus: 'OPEN', @@ -985,9 +987,9 @@ describe('modules/platform/codecommit/index', () => { codeCommitClient .on(ListPullRequestsCommand) .resolvesOnce({ pullRequestIds: ['42'] }); - const prRes = { + const prRes: GetPullRequestOutput = { pullRequest: { - number: '42', + pullRequestId: '42', title: 'someTitle', pullRequestStatus: 'OPEN', pullRequestTargets: [ @@ -1153,9 +1155,9 @@ describe('modules/platform/codecommit/index', () => { codeCommitClient .on(ListPullRequestsCommand) .resolvesOnce({ pullRequestIds: ['42'] }); - const prRes = { + const prRes: GetPullRequestOutput = { pullRequest: { - number: '42', + pullRequestId: '42', title: 'someTitle', pullRequestStatus: 'OPEN', pullRequestTargets: [ diff --git a/lib/modules/platform/gerrit/client.spec.ts b/lib/modules/platform/gerrit/client.spec.ts index 9ebdc67b7a8ed3..59a5dcd788ca21 100644 --- a/lib/modules/platform/gerrit/client.spec.ts +++ b/lib/modules/platform/gerrit/client.spec.ts @@ -118,6 +118,22 @@ describe('modules/platform/gerrit/client', () => { prTitle: 'fix(deps): update dependency react-router-dom to v6.21.2', }, ], + [ + 'message:"fix(deps): update dependency react-router-dom to ~> v6.21.2"', + { + branchName: 'dependency-xyz', + prTitle: + 'fix(deps): update dependency react-router-dom to ~> "v6.21.2"', + }, + ], + [ + 'message:"fix(deps): update dependency react-router-dom to ~> v6.21.2"', + { + branchName: 'dependency-xyz', + prTitle: + 'fix(deps): "update dependency react-router-dom to ~> "v6.21.2""', + }, + ], ])( 'query contains %p', async (expectedQueryPart: string, config: GerritFindPRConfig) => { @@ -144,7 +160,7 @@ describe('modules/platform/gerrit/client', () => { httpMock .scope(gerritEndpointUrl) .get( - '/a/changes/123456?o=SUBMITTABLE&o=CHECK&o=MESSAGES&o=DETAILED_ACCOUNTS&o=LABELS&o=CURRENT_ACTIONS&o=CURRENT_REVISION', + '/a/changes/123456?o=SUBMITTABLE&o=CHECK&o=MESSAGES&o=DETAILED_ACCOUNTS&o=LABELS&o=CURRENT_ACTIONS&o=CURRENT_REVISION&o=CURRENT_COMMIT', ) .reply(200, gerritRestResponse(change), jsonResultHeader); await expect(client.getChange(123456)).resolves.toEqual(change); @@ -199,17 +215,24 @@ describe('modules/platform/gerrit/client', () => { }); }); - describe('updateCommitMessage', () => { - it('updateCommitMessage - success', async () => { + describe('updateChangeSubject', () => { + it('updateChangeSubject - success', async () => { const change = partial({}); + const newSubject = 'new subject'; + const previousSubject = 'some subject'; + const previousBody = `some body\n\nChange-Id: some-change-id\n`; httpMock .scope(gerritEndpointUrl) .put('/a/changes/123456/message', { - message: `new message\n\nChange-Id: changeID\n`, + message: `${newSubject}\n\n${previousBody}`, }) .reply(200, gerritRestResponse(change), jsonResultHeader); await expect( - client.updateCommitMessage(123456, 'changeID', 'new message'), + client.updateChangeSubject( + 123456, + `${previousSubject}\n\n${previousBody}`, + 'new subject', + ), ).toResolve(); }); }); diff --git a/lib/modules/platform/gerrit/client.ts b/lib/modules/platform/gerrit/client.ts index 087d27097c72f3..3182db64db37b2 100644 --- a/lib/modules/platform/gerrit/client.ts +++ b/lib/modules/platform/gerrit/client.ts @@ -1,6 +1,7 @@ import { REPOSITORY_ARCHIVED } from '../../../constants/error-messages'; import { logger } from '../../../logger'; import { GerritHttp } from '../../../util/http/gerrit'; +import { regEx } from '../../../util/regex'; import type { GerritAccountInfo, GerritBranchInfo, @@ -12,6 +13,8 @@ import type { } from './types'; import { mapPrStateToGerritFilter } from './utils'; +const QUOTES_REGEX = regEx('"', 'g'); + class GerritClient { private requestDetails = [ 'SUBMITTABLE', //include the submittable field in ChangeInfo, which can be used to tell if the change is reviewed and ready for submit. @@ -21,6 +24,7 @@ class GerritClient { 'LABELS', 'CURRENT_ACTIONS', //to check if current_revision can be "rebased" 'CURRENT_REVISION', //get RevisionInfo::ref to fetch + 'CURRENT_COMMIT', // to get the commit message ] as const; private gerritHttp = new GerritHttp(); @@ -100,15 +104,17 @@ class GerritClient { }); } - async updateCommitMessage( + async updateChangeSubject( number: number, - gerritChangeID: string, - prTitle: string, + currentMessage: string, + newSubject: string, ): Promise { - await this.setCommitMessage( - number, - `${prTitle}\n\nChange-Id: ${gerritChangeID}\n`, + // Replace first line of the commit message with the new subject + const newMessage = currentMessage.replace( + new RegExp(`^.*$`, 'm'), + newSubject, ); + await this.setCommitMessage(number, newMessage); } async getMessages(changeNumber: number): Promise { @@ -235,8 +241,10 @@ class GerritClient { filters.push(`label:Code-Review=${searchConfig.label}`); } if (searchConfig.prTitle) { + // escaping support in Gerrit is not great, so we need to remove quotes + // special characters are ignored anyway in the search so it does not create any issues filters.push( - `message:${encodeURIComponent('"' + searchConfig.prTitle + '"')}`, + `message:${encodeURIComponent('"' + searchConfig.prTitle.replace(QUOTES_REGEX, '') + '"')}`, ); } return filters; diff --git a/lib/modules/platform/gerrit/index.spec.ts b/lib/modules/platform/gerrit/index.spec.ts index 7b7c7027e7c79e..17ee4188cde69d 100644 --- a/lib/modules/platform/gerrit/index.spec.ts +++ b/lib/modules/platform/gerrit/index.spec.ts @@ -11,6 +11,7 @@ import type { GerritLabelInfo, GerritLabelTypeInfo, GerritProjectInfo, + GerritRevisionInfo, } from './types'; import { TAG_PULL_REQUEST_BODY, mapGerritChangeToPr } from './utils'; import { writeToConfig } from '.'; @@ -187,21 +188,39 @@ describe('modules/platform/gerrit/index', () => { }); it('updatePr() - new prTitle => copy to commit msg', async () => { + const oldSubject = 'old title'; + const oldMessage = `${oldSubject}\n\nsome body\n\nChange-Id: ...`; const change = partial({ - change_id: '...', - subject: 'old title', + subject: oldSubject, + current_revision: 'some-revision', + revisions: { + 'some-revision': partial({ + commit: { + message: oldMessage, + }, + }), + }, }); clientMock.getChange.mockResolvedValueOnce(change); await gerrit.updatePr({ number: 123456, prTitle: 'new title' }); - expect(clientMock.updateCommitMessage).toHaveBeenCalledWith( + expect(clientMock.updateChangeSubject).toHaveBeenCalledWith( 123456, - '...', + oldMessage, 'new title', ); }); it('updatePr() - auto approve enabled', async () => { - const change = partial({}); + const change = partial({ + current_revision: 'some-revision', + revisions: { + 'some-revision': partial({ + commit: { + message: 'some message', + }, + }), + }, + }); clientMock.getChange.mockResolvedValueOnce(change); await gerrit.updatePr({ number: 123456, @@ -225,7 +244,16 @@ describe('modules/platform/gerrit/index', () => { }); it('updatePr() - existing prBody found in change.messages => nothing todo...', async () => { - const change = partial({}); + const change = partial({ + current_revision: 'some-revision', + revisions: { + 'some-revision': partial({ + commit: { + message: 'some message', + }, + }), + }, + }); clientMock.getChange.mockResolvedValueOnce(change); clientMock.getMessages.mockResolvedValueOnce([ partial({ @@ -279,9 +307,18 @@ describe('modules/platform/gerrit/index', () => { gerrit.writeToConfig({ labels: {} }); }); + const message = 'some subject\n\nsome body\n\nChange-Id: some-change-id'; + const change = partial({ _number: 123456, - change_id: '...', + current_revision: 'some-revision', + revisions: { + 'some-revision': partial({ + commit: { + message, + }, + }), + }, }); beforeEach(() => { @@ -312,9 +349,9 @@ describe('modules/platform/gerrit/index', () => { TAG_PULL_REQUEST_BODY, ); expect(clientMock.approveChange).not.toHaveBeenCalled(); - expect(clientMock.updateCommitMessage).toHaveBeenCalledWith( + expect(clientMock.updateChangeSubject).toHaveBeenCalledWith( 123456, - '...', + message, 'title', ); }); diff --git a/lib/modules/platform/gerrit/index.ts b/lib/modules/platform/gerrit/index.ts index 5e02b30abc677b..ddd058f21e11d2 100644 --- a/lib/modules/platform/gerrit/index.ts +++ b/lib/modules/platform/gerrit/index.ts @@ -155,9 +155,9 @@ export async function updatePr(prConfig: UpdatePrConfig): Promise { logger.debug(`updatePr(${prConfig.number}, ${prConfig.prTitle})`); const change = await client.getChange(prConfig.number); if (change.subject !== prConfig.prTitle) { - await client.updateCommitMessage( + await client.updateChangeSubject( prConfig.number, - change.change_id, + change.revisions[change.current_revision].commit.message, prConfig.prTitle, ); } @@ -200,9 +200,9 @@ export async function createPr(prConfig: CreatePRConfig): Promise { } //Workaround for "Known Problems.1" if (pr.subject !== prConfig.prTitle) { - await client.updateCommitMessage( + await client.updateChangeSubject( pr._number, - pr.change_id, + pr.revisions[pr.current_revision].commit.message, prConfig.prTitle, ); } diff --git a/lib/modules/platform/gerrit/readme.md b/lib/modules/platform/gerrit/readme.md index b6ff385e9f522d..01a539311cf65d 100644 --- a/lib/modules/platform/gerrit/readme.md +++ b/lib/modules/platform/gerrit/readme.md @@ -42,7 +42,7 @@ It works similar to the default option `"pr"`. You can use the `statusCheckNames` configuration to map any of the available branch checks (like `minimumReleaseAge`, `mergeConfidence`, and so on) to a Gerrit label. -For example, if you want to use the [Merge Confidence](https://docs.renovatebot.com/merge-confidence/) feature and map the result of the Merge Confidence check to your Gerrit label "Renovate-Merge-Confidence" you can configure: +For example, if you want to use the [Merge Confidence](../../../merge-confidence.md) feature and map the result of the Merge Confidence check to your Gerrit label "Renovate-Merge-Confidence" you can configure: ```json { diff --git a/lib/modules/platform/gerrit/scm.ts b/lib/modules/platform/gerrit/scm.ts index 40fc56c88aab2e..f4369fa39e2877 100644 --- a/lib/modules/platform/gerrit/scm.ts +++ b/lib/modules/platform/gerrit/scm.ts @@ -34,7 +34,7 @@ export class GerritScm extends DefaultGitScm { .findChanges(repository, searchConfig, true) .then((res) => res.pop()); if (change) { - return change.current_revision! as LongCommitSha; + return change.current_revision as LongCommitSha; } return git.getBranchCommit(branchName); } @@ -52,7 +52,7 @@ export class GerritScm extends DefaultGitScm { .findChanges(repository, searchConfig, true) .then((res) => res.pop()); if (change) { - const currentGerritPatchset = change.revisions![change.current_revision!]; + const currentGerritPatchset = change.revisions[change.current_revision]; return currentGerritPatchset.actions?.['rebase'].enabled === true; } return true; @@ -85,7 +85,7 @@ export class GerritScm extends DefaultGitScm { .findChanges(repository, searchConfig, true) .then((res) => res.pop()); if (change) { - const currentGerritPatchset = change.revisions![change.current_revision!]; + const currentGerritPatchset = change.revisions[change.current_revision]; return currentGerritPatchset.uploader.username !== username; } return false; @@ -153,9 +153,7 @@ export class GerritScm extends DefaultGitScm { .findChanges(repository, searchConfig, true) .then((res) => res.pop()); if (change) { - return super.mergeToLocal( - change.revisions![change.current_revision!].ref, - ); + return super.mergeToLocal(change.revisions[change.current_revision].ref); } return super.mergeToLocal(branchName); } diff --git a/lib/modules/platform/gerrit/types.ts b/lib/modules/platform/gerrit/types.ts index 7ec71999f4f1e2..3aae7d47e6831e 100644 --- a/lib/modules/platform/gerrit/types.ts +++ b/lib/modules/platform/gerrit/types.ts @@ -43,14 +43,18 @@ export interface GerritChange { labels?: Record; reviewers?: Record; messages?: GerritChangeMessageInfo[]; - current_revision?: string; + current_revision: string; /** * All patch sets of this change as a map that maps the commit ID of the patch set to a RevisionInfo entity. */ - revisions?: Record; + revisions: Record; problems: unknown[]; } +export interface GerritCommitInfo { + message: string; +} + export interface GerritRevisionInfo { uploader: GerritAccountInfo; /** @@ -58,6 +62,7 @@ export interface GerritRevisionInfo { */ ref: string; actions?: Record; + commit: GerritCommitInfo; } export interface GerritChangeMessageInfo { diff --git a/lib/modules/platform/gitea/gitea-helper.spec.ts b/lib/modules/platform/gitea/gitea-helper.spec.ts index 28bcf498247421..9f06e6750abf06 100644 --- a/lib/modules/platform/gitea/gitea-helper.spec.ts +++ b/lib/modules/platform/gitea/gitea-helper.spec.ts @@ -1,4 +1,5 @@ import * as httpMock from '../../../../test/http-mock'; +import { partial } from '../../../../test/util'; import type { LongCommitSha } from '../../../util/git/types'; import { setBaseUrl } from '../../../util/http/gitea'; import { toBase64 } from '../../../util/string'; @@ -22,6 +23,7 @@ import { getRepoLabels, getVersion, mergePR, + orgListRepos, requestPrReviewers, searchIssues, searchRepos, @@ -66,7 +68,7 @@ describe('modules/platform/gitea/gitea-helper', () => { email: 'renovate@example.com', }; - const mockRepo: Repo = { + const mockRepo: Repo = partial({ id: 123, allow_rebase: true, allow_rebase_explicit: true, @@ -87,7 +89,8 @@ describe('modules/platform/gitea/gitea-helper', () => { admin: false, }, has_issues: true, - }; + has_pull_requests: true, + }); const otherMockRepo: Repo = { ...mockRepo, @@ -250,6 +253,15 @@ describe('modules/platform/gitea/gitea-helper', () => { }); }); + describe('orgListRepos', () => { + it('should call /api/v1/orgs/[organization]/repos endpoint', async () => { + httpMock.scope(baseUrl).get('/orgs/some/repos').reply(200, mockRepo); + + const res = await orgListRepos('some'); + expect(res).toEqual(mockRepo); + }); + }); + describe('getRepo', () => { it('should call /api/v1/repos/[repo] endpoint', async () => { httpMock @@ -377,8 +389,7 @@ describe('modules/platform/gitea/gitea-helper', () => { .patch(`/repos/${mockRepo.full_name}/pulls/${mockPR.number}`) .reply(200); - const res = await closePR(mockRepo.full_name, mockPR.number); - expect(res).toBeUndefined(); + await expect(closePR(mockRepo.full_name, mockPR.number)).toResolve(); }); }); @@ -389,10 +400,11 @@ describe('modules/platform/gitea/gitea-helper', () => { .post(`/repos/${mockRepo.full_name}/pulls/${mockPR.number}/merge`) .reply(200); - const res = await mergePR(mockRepo.full_name, mockPR.number, { - Do: 'rebase', - }); - expect(res).toBeUndefined(); + await expect( + mergePR(mockRepo.full_name, mockPR.number, { + Do: 'rebase', + }), + ).toResolve(); }); }); @@ -569,12 +581,9 @@ describe('modules/platform/gitea/gitea-helper', () => { ) .reply(200); - const res = await unassignLabel( - mockRepo.full_name, - mockIssue.number, - mockLabel.id, - ); - expect(res).toBeUndefined(); + await expect( + unassignLabel(mockRepo.full_name, mockIssue.number, mockLabel.id), + ).toResolve(); }); }); diff --git a/lib/modules/platform/gitea/gitea-helper.ts b/lib/modules/platform/gitea/gitea-helper.ts index 3b25b850efeffc..fffa9dc5d13106 100644 --- a/lib/modules/platform/gitea/gitea-helper.ts +++ b/lib/modules/platform/gitea/gitea-helper.ts @@ -75,6 +75,19 @@ export async function searchRepos( return res.body.data; } +export async function orgListRepos( + organization: string, + options?: GiteaHttpOptions, +): Promise { + const url = `${API_PATH}/orgs/${organization}/repos`; + const res = await giteaHttp.getJson(url, { + ...options, + paginate: true, + }); + + return res.body; +} + export async function getRepo( repoPath: string, options?: GiteaHttpOptions, diff --git a/lib/modules/platform/gitea/index.spec.ts b/lib/modules/platform/gitea/index.spec.ts index 2a8de9e8c40bad..f74c4270c15606 100644 --- a/lib/modules/platform/gitea/index.spec.ts +++ b/lib/modules/platform/gitea/index.spec.ts @@ -22,6 +22,7 @@ import type { Label, PR, Repo, + RepoPermission, User, } from './types'; @@ -39,6 +40,14 @@ describe('modules/platform/gitea/index', () => { let hostRules: typeof import('../../../util/host-rules'); let memCache: typeof import('../../../util/cache/memory'); + function mockedRepo(opts: Partial): Repo { + return partial({ + permissions: partial({ push: true, pull: true }), + has_pull_requests: true, + ...opts, + }); + } + const mockCommitHash = '0d9c7726c3d628b7e28af234595cfd20febdbf8e' as LongCommitSha; @@ -49,28 +58,29 @@ describe('modules/platform/gitea/index', () => { email: 'renovate@example.com', }; - const mockRepo = partial({ + const mockRepo = mockedRepo({ allow_rebase: true, clone_url: 'https://gitea.renovatebot.com/some/repo.git', ssh_url: 'git@gitea.renovatebot.com/some/repo.git', default_branch: 'master', full_name: 'some/repo', - permissions: { - pull: true, - push: true, - admin: false, - }, }); type MockPr = PR & Required>; const mockRepos: Repo[] = [ - partial({ full_name: 'a/b' }), - partial({ full_name: 'c/d' }), - partial({ full_name: 'e/f', mirror: true }), + mockedRepo({ full_name: 'a/b' }), + mockedRepo({ full_name: 'c/d' }), + mockedRepo({ full_name: 'e/f', mirror: true }), ]; - const mockTopicRepos: Repo[] = [partial({ full_name: 'a/b' })]; + const mockTopicRepos: Repo[] = [mockedRepo({ full_name: 'a/b' })]; + + const mockNamespaceRepos: Repo[] = [ + mockedRepo({ full_name: 'org1/repo' }), + mockedRepo({ full_name: 'org2/repo' }), + mockedRepo({ full_name: 'org2/repo2', archived: true }), + ]; const mockPRs: MockPr[] = [ partial({ @@ -106,6 +116,12 @@ describe('modules/platform/gitea/index', () => { sha: 'other-head-sha' as LongCommitSha, repo: partial({ full_name: mockRepo.full_name }), }, + labels: [ + { + id: 1, + name: 'bug', + }, + ], }), partial({ number: 3, @@ -208,9 +224,6 @@ describe('modules/platform/gitea/index', () => { hostRules.clear(); setBaseUrl('https://gitea.renovatebot.com/'); - - delete process.env.RENOVATE_X_AUTODISCOVER_REPO_SORT; - delete process.env.RENOVATE_X_AUTODISCOVER_REPO_ORDER; }); async function initFakePlatform( @@ -390,9 +403,21 @@ describe('modules/platform/gitea/index', () => { expect(repos).toEqual(['a/b']); }); + it('should query the organization endpoint for each namespace', async () => { + const scope = httpMock.scope('https://gitea.com/api/v1'); + + scope.get('/orgs/org1/repos').reply(200, mockNamespaceRepos); + scope.get('/orgs/org2/repos').reply(200, mockNamespaceRepos); + + await initFakePlatform(scope); + + const repos = await gitea.getRepos({ + namespaces: ['org1', 'org2'], + }); + expect(repos).toEqual(['org1/repo', 'org2/repo']); + }); + it('Sorts repos', async () => { - process.env.RENOVATE_X_AUTODISCOVER_REPO_SORT = 'updated'; - process.env.RENOVATE_X_AUTODISCOVER_REPO_ORDER = 'desc'; const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/search') @@ -408,7 +433,10 @@ describe('modules/platform/gitea/index', () => { }); await initFakePlatform(scope); - const repos = await gitea.getRepos(); + const repos = await gitea.getRepos({ + sort: 'updated', + order: 'desc', + }); expect(repos).toEqual(['a/b', 'c/d']); }); }); @@ -487,6 +515,20 @@ describe('modules/platform/gitea/index', () => { ); }); + it('should abort when repo has pulls disabled', async () => { + const scope = httpMock + .scope('https://gitea.com/api/v1') + .get(`/repos/${initRepoCfg.repository}`) + .reply(200, { + ...mockRepo, + has_pull_requests: false, + }); + await initFakePlatform(scope); + await expect(gitea.initRepo(initRepoCfg)).rejects.toThrow( + REPOSITORY_BLOCKED, + ); + }); + it('should abort when repo has no available merge methods', async () => { const scope = httpMock .scope('https://gitea.com/api/v1') @@ -1567,6 +1609,8 @@ describe('modules/platform/gitea/index', () => { it('should use platform automerge', async () => { memCache.set('gitea-pr-cache-synced', true); + const helper = await import('./gitea-helper'); + const mergePR = jest.spyOn(helper, 'mergePR'); const scope = httpMock .scope('https://gitea.com/api/v1') .post('/repos/some/repo/pulls') @@ -1588,6 +1632,63 @@ describe('modules/platform/gitea/index', () => { number: 42, title: 'pr-title', }); + expect(mergePR).toHaveBeenCalled(); + }); + + it('should use platform automerge on forgejo v7', async () => { + memCache.set('gitea-pr-cache-synced', true); + const helper = await import('./gitea-helper'); + const mergePR = jest.spyOn(helper, 'mergePR'); + const scope = httpMock + .scope('https://gitea.com/api/v1') + .post('/repos/some/repo/pulls') + .reply(200, mockNewPR) + .post('/repos/some/repo/pulls/42/merge') + .reply(200); + await initFakePlatform(scope, '7.0.0-dev-2136-f075579c95+gitea-1.22.0'); + await initFakeRepo(scope); + + const res = await gitea.createPr({ + sourceBranch: mockNewPR.head.label, + targetBranch: 'master', + prTitle: mockNewPR.title, + prBody: mockNewPR.body, + platformOptions: { usePlatformAutomerge: true }, + }); + + expect(res).toMatchObject({ + number: 42, + title: 'pr-title', + }); + expect(mergePR).toHaveBeenCalled(); + }); + + it('should use platform automerge on forgejo v7 LTS', async () => { + memCache.set('gitea-pr-cache-synced', true); + const helper = await import('./gitea-helper'); + const mergePR = jest.spyOn(helper, 'mergePR'); + const scope = httpMock + .scope('https://gitea.com/api/v1') + .post('/repos/some/repo/pulls') + .reply(200, mockNewPR) + .post('/repos/some/repo/pulls/42/merge') + .reply(200); + await initFakePlatform(scope, '7.0.0+LTS-gitea-1.22.0'); + await initFakeRepo(scope); + + const res = await gitea.createPr({ + sourceBranch: mockNewPR.head.label, + targetBranch: 'master', + prTitle: mockNewPR.title, + prBody: mockNewPR.body, + platformOptions: { usePlatformAutomerge: true }, + }); + + expect(res).toMatchObject({ + number: 42, + title: 'pr-title', + }); + expect(mergePR).toHaveBeenCalled(); }); it('continues on platform automerge error', async () => { @@ -1830,6 +1931,87 @@ describe('modules/platform/gitea/index', () => { }), ).toResolve(); }); + + it('should update labels', async () => { + const updatedMockPR = partial({ + ...mockPRs[0], + number: 1, + title: 'New Title', + body: 'New Body', + state: 'open', + labels: [ + { + id: 1, + name: 'some-label', + }, + ], + }); + const scope = httpMock + .scope('https://gitea.com/api/v1') + .get('/repos/some/repo/pulls') + .query({ state: 'all', sort: 'recentupdate' }) + .reply(200, mockPRs) + .get('/repos/some/repo/labels') + .reply(200, mockRepoLabels) + .get('/orgs/some/labels') + .reply(200, mockOrgLabels) + .patch('/repos/some/repo/pulls/1') + .reply(200, updatedMockPR); + + await initFakePlatform(scope); + await initFakeRepo(scope); + await expect( + gitea.updatePr({ + number: 1, + prTitle: 'New Title', + prBody: 'New Body', + state: 'open', + labels: ['some-label'], + }), + ).toResolve(); + }); + + it('should log a warning if labels could not be looked up', async () => { + const updatedMockPR = partial({ + ...mockPRs[0], + number: 1, + title: 'New Title', + body: 'New Body', + state: 'open', + labels: [ + { + id: 1, + name: 'some-label', + }, + ], + }); + const scope = httpMock + .scope('https://gitea.com/api/v1') + .get('/repos/some/repo/pulls') + .query({ state: 'all', sort: 'recentupdate' }) + .reply(200, mockPRs) + .get('/repos/some/repo/labels') + .reply(200, mockRepoLabels) + .get('/orgs/some/labels') + .reply(200, mockOrgLabels) + .patch('/repos/some/repo/pulls/1') + .reply(200, updatedMockPR); + + await initFakePlatform(scope); + await initFakeRepo(scope); + await expect( + gitea.updatePr({ + number: 1, + prTitle: 'New Title', + prBody: 'New Body', + state: 'open', + labels: ['some-label', 'unavailable-label'], + }), + ).toResolve(); + expect(logger.warn).toHaveBeenCalledWith( + 'Some labels could not be looked up. Renovate may halt label updates assuming changes by others.', + ); + }); }); describe('mergePr', () => { diff --git a/lib/modules/platform/gitea/index.ts b/lib/modules/platform/gitea/index.ts index dd03749ca263f5..b4859dfe99c140 100644 --- a/lib/modules/platform/gitea/index.ts +++ b/lib/modules/platform/gitea/index.ts @@ -34,6 +34,8 @@ import type { Pr, RepoParams, RepoResult, + RepoSortMethod, + SortMethod, UpdatePrConfig, } from '../types'; import { repoFingerprint } from '../util'; @@ -49,8 +51,6 @@ import type { PRMergeMethod, PRUpdateParams, Repo, - RepoSortMethod, - SortMethod, } from './types'; import { DRAFT_PREFIX, @@ -59,6 +59,7 @@ import { smartLinks, toRenovatePR, trimTrailingApiPath, + usableRepo, } from './utils'; interface GiteaRepoConfig { @@ -78,6 +79,7 @@ const defaults = { hostType: 'gitea', endpoint: 'https://gitea.com/', version: '0.0.0', + isForgejo: false, }; let config: GiteaRepoConfig = {} as any; @@ -157,7 +159,17 @@ async function lookupLabelByName(name: string): Promise { return labelList.find((l) => l.name === name)?.id ?? null; } -async function fetchRepositories(topic?: string): Promise { +interface FetchRepositoriesArgs { + topic?: string; + sort?: RepoSortMethod; + order?: SortMethod; +} + +async function fetchRepositories({ + topic, + sort, + order, +}: FetchRepositoriesArgs): Promise { const repos = await helper.searchRepos({ uid: botUserID, archived: false, @@ -165,14 +177,14 @@ async function fetchRepositories(topic?: string): Promise { topic: true, q: topic, }), - ...(process.env.RENOVATE_X_AUTODISCOVER_REPO_SORT && { - sort: process.env.RENOVATE_X_AUTODISCOVER_REPO_SORT as RepoSortMethod, + ...(sort && { + sort, }), - ...(process.env.RENOVATE_X_AUTODISCOVER_REPO_ORDER && { - order: process.env.RENOVATE_X_AUTODISCOVER_REPO_ORDER as SortMethod, + ...(order && { + order, }), }); - return repos.filter((r) => !r.mirror).map((r) => r.full_name); + return repos.filter(usableRepo).map((r) => r.full_name); } const platform: Platform = { @@ -200,6 +212,12 @@ const platform: Platform = { botUserID = user.id; botUserName = user.username; defaults.version = await helper.getVersion({ token }); + if (defaults.version?.includes('gitea-')) { + defaults.version = defaults.version.substring( + defaults.version.indexOf('gitea-') + 6, + ); + defaults.isForgejo = true; + } } catch (err) { logger.debug( { err }, @@ -255,28 +273,29 @@ const platform: Platform = { // Ensure appropriate repository state and permissions if (repo.archived) { - logger.debug( - 'Repository is archived - throwing error to abort renovation', - ); + logger.debug('Repository is archived - aborting renovation'); throw new Error(REPOSITORY_ARCHIVED); } if (repo.mirror) { - logger.debug( - 'Repository is a mirror - throwing error to abort renovation', - ); + logger.debug('Repository is a mirror - aborting renovation'); throw new Error(REPOSITORY_MIRRORED); } - if (!repo.permissions.pull || !repo.permissions.push) { + if (repo.permissions.pull === false || repo.permissions.push === false) { logger.debug( - 'Repository does not permit pull and push - throwing error to abort renovation', + 'Repository does not permit pull or push - aborting renovation', ); throw new Error(REPOSITORY_ACCESS_FORBIDDEN); } if (repo.empty) { - logger.debug('Repository is empty - throwing error to abort renovation'); + logger.debug('Repository is empty - aborting renovation'); throw new Error(REPOSITORY_EMPTY); } + if (repo.has_pull_requests === false) { + logger.debug('Repo has disabled pull requests - aborting renovation'); + throw new Error(REPOSITORY_BLOCKED); + } + if (repo.allow_rebase) { config.mergeMethod = 'rebase'; } else if (repo.allow_rebase_explicit) { @@ -287,7 +306,7 @@ const platform: Platform = { config.mergeMethod = 'merge'; } else { logger.debug( - 'Repository has no allowed merge methods - throwing error to abort renovation', + 'Repository has no allowed merge methods - aborting renovation', ); throw new Error(REPOSITORY_BLOCKED); } @@ -319,12 +338,40 @@ const platform: Platform = { async getRepos(config?: AutodiscoverConfig): Promise { logger.debug('Auto-discovering Gitea repositories'); try { - if (!config?.topics) { - return await fetchRepositories(); + if (config?.topics) { + logger.debug({ topics: config.topics }, 'Auto-discovering by topics'); + const fetchRepoArgs: FetchRepositoriesArgs[] = config.topics.map( + (topic) => { + return { + topic, + sort: config.sort, + order: config.order, + }; + }, + ); + const repos = await map(fetchRepoArgs, fetchRepositories); + return deduplicateArray(repos.flat()); + } else if (config?.namespaces) { + logger.debug( + { namespaces: config.namespaces }, + 'Auto-discovering by organization', + ); + const repos = await map( + config.namespaces, + async (organization: string) => { + const orgRepos = await helper.orgListRepos(organization); + return orgRepos + .filter((r) => !r.mirror && !r.archived) + .map((r) => r.full_name); + }, + ); + return deduplicateArray(repos.flat()); + } else { + return await fetchRepositories({ + sort: config?.sort, + order: config?.order, + }); } - - const repos = await map(config.topics, fetchRepositories); - return deduplicateArray(repos.flat()); } catch (err) { logger.error({ err }, 'Gitea getRepos() error'); throw err; @@ -490,7 +537,7 @@ const platform: Platform = { logger.debug(`Creating pull request: ${title} (${head} => ${base})`); try { const labels = Array.isArray(labelNames) - ? await Promise.all(labelNames.map(lookupLabelByName)) + ? await map(labelNames, lookupLabelByName) : []; const gpr = await helper.createPR(config.repository, { base, @@ -583,6 +630,7 @@ const platform: Platform = { number, prTitle, prBody: body, + labels, state, targetBranch, }: UpdatePrConfig): Promise { @@ -600,6 +648,24 @@ const platform: Platform = { prUpdateParams.base = targetBranch; } + /** + * Update PR labels. + * In the Gitea API, labels are replaced on each update if the field is present. + * If the field is not present (i.e., undefined), labels aren't updated. + * However, the labels array must contain label IDs instead of names, + * so a lookup is performed to fetch the details (including the ID) of each label. + */ + if (Array.isArray(labels)) { + prUpdateParams.labels = (await map(labels, lookupLabelByName)).filter( + is.number, + ); + if (labels.length !== prUpdateParams.labels.length) { + logger.warn( + 'Some labels could not be looked up. Renovate may halt label updates assuming changes by others.', + ); + } + } + const gpr = await helper.updatePR( config.repository, number, diff --git a/lib/modules/platform/gitea/pr-cache.ts b/lib/modules/platform/gitea/pr-cache.ts index 02b080fa786585..d3192d767e14f3 100644 --- a/lib/modules/platform/gitea/pr-cache.ts +++ b/lib/modules/platform/gitea/pr-cache.ts @@ -19,7 +19,9 @@ export class GiteaPrCache { const repoCache = getCache(); repoCache.platform ??= {}; repoCache.platform.gitea ??= {}; - let pullRequestCache = repoCache.platform.gitea.pullRequestsCache; + let pullRequestCache = repoCache.platform.gitea.pullRequestsCache as + | GiteaPrCacheData + | undefined; if (!pullRequestCache || pullRequestCache.author !== author) { pullRequestCache = { items: {}, diff --git a/lib/modules/platform/gitea/readme.md b/lib/modules/platform/gitea/readme.md index 5e1ac67c3c74b4..8159630fc61343 100644 --- a/lib/modules/platform/gitea/readme.md +++ b/lib/modules/platform/gitea/readme.md @@ -38,8 +38,15 @@ If you use Gitea packages, add the `read:packages` scope. - none -## Repo autodiscover sorting +## Repo autodiscover + +Renovate can discover repositories on Gitea using the `autodiscover` feature. +Repositories are ignored when one of the following conditions is met: + +- The repository is a `mirror` +- We do not have push or pull permissions to that repository +- Pull requests are disabled for that repository You can change the default server-side sort method and order for autodiscover API. -Set those via [`RENOVATE_X_AUTODISCOVER_REPO_SORT`](../../../self-hosted-experimental.md#renovate_x_autodiscover_repo_sort) and [`RENOVATE_X_AUTODISCOVER_REPO_ORDER`](../../../self-hosted-experimental.md#renovate_x_autodiscover_repo_order). +Set those via [`autodiscoverRepoSort`](../../../self-hosted-configuration.md#autodiscoverreposort) and [`autodiscoverRepoOrder`](../../../self-hosted-configuration.md#autodiscoverrepoorder). Read the [Gitea swagger docs](https://try.gitea.io/api/swagger#/repository/repoSearch) for more details. diff --git a/lib/modules/platform/gitea/types.ts b/lib/modules/platform/gitea/types.ts index 877ea4436d0881..3025a89633ec04 100644 --- a/lib/modules/platform/gitea/types.ts +++ b/lib/modules/platform/gitea/types.ts @@ -1,5 +1,5 @@ import type { LongCommitSha } from '../../../util/git/types'; -import type { Pr } from '../types'; +import type { Pr, RepoSortMethod, SortMethod } from '../types'; export interface PrReviewersParams { reviewers?: string[]; @@ -17,6 +17,10 @@ export type CommitStatusType = | 'unknown'; export type PRMergeMethod = 'merge' | 'rebase' | 'rebase-merge' | 'squash'; +export interface GiteaLabel { + id: number; + name: string; +} export interface PR { number: number; state: PRState; @@ -40,6 +44,10 @@ export interface PR { }; assignees?: any[]; user?: { username?: string }; + + // labels returned from the Gitea API are represented as an array of objects + // ref: https://docs.gitea.com/api/1.20/#tag/repository/operation/repoGetPullRequest + labels?: GiteaLabel[]; } export interface Issue { @@ -66,8 +74,10 @@ export interface Repo { allow_squash_merge: boolean; archived: boolean; clone_url?: string; + default_merge_style: string; external_tracker?: unknown; has_issues: boolean; + has_pull_requests: boolean; ssh_url?: string; default_branch: string; empty: boolean; @@ -137,10 +147,6 @@ export interface CombinedCommitStatus { statuses: CommitStatus[]; } -export type RepoSortMethod = 'alpha' | 'created' | 'updated' | 'size' | 'id'; - -export type SortMethod = 'asc' | 'desc'; - export interface RepoSearchParams { uid?: number; archived?: boolean; diff --git a/lib/modules/platform/gitea/utils.spec.ts b/lib/modules/platform/gitea/utils.spec.ts index 60806698f26296..48d8dcd8deb26e 100644 --- a/lib/modules/platform/gitea/utils.spec.ts +++ b/lib/modules/platform/gitea/utils.spec.ts @@ -1,7 +1,12 @@ import { partial } from '../../../../test/util'; import { CONFIG_GIT_URL_UNAVAILABLE } from '../../../constants/error-messages'; import type { Repo } from './types'; -import { getMergeMethod, getRepoUrl, trimTrailingApiPath } from './utils'; +import { + getMergeMethod, + getRepoUrl, + trimTrailingApiPath, + usableRepo, +} from './utils'; describe('modules/platform/gitea/utils', () => { const mockRepo = partial({ @@ -15,6 +20,7 @@ describe('modules/platform/gitea/utils', () => { push: true, admin: false, }, + has_pull_requests: true, }); it('trimTrailingApiPath', () => { @@ -55,4 +61,29 @@ describe('modules/platform/gitea/utils', () => { `('getMergeMethod("$value") == "$expected"', ({ value, expected }) => { expect(getMergeMethod(value)).toBe(expected); }); + + describe('usableRepo', () => { + it('should return true when repo is usable', () => { + expect(usableRepo(mockRepo)).toBe(true); + }); + + it('should return false when repo lacks permissions', () => { + expect( + usableRepo({ + ...mockRepo, + permissions: { pull: false, push: false, admin: true }, + }), + ).toBe(false); + expect( + usableRepo({ + ...mockRepo, + permissions: { pull: true, push: false, admin: true }, + }), + ).toBe(false); + }); + + it('should return false when repo has disabled pull requests', () => { + expect(usableRepo({ ...mockRepo, has_pull_requests: false })).toBe(false); + }); + }); }); diff --git a/lib/modules/platform/gitea/utils.ts b/lib/modules/platform/gitea/utils.ts index d4e5bd5bb32919..24d2052d8d4659 100644 --- a/lib/modules/platform/gitea/utils.ts +++ b/lib/modules/platform/gitea/utils.ts @@ -119,8 +119,10 @@ export function toRenovatePR(data: PR, author: string | null): Pr | null { title = title.substring(DRAFT_PREFIX.length); isDraft = true; } + const labels = (data?.labels ?? []).map((l) => l.name); return { + labels, number: data.number, state: data.state, title, @@ -137,3 +139,33 @@ export function toRenovatePR(data: PR, author: string | null): Pr | null { hasAssignees: !!(data.assignee?.login ?? is.nonEmptyArray(data.assignees)), }; } + +/** + * Check if a repository is usable. + * A repo isn't usable if one of the following conditions is met: + * - The repo is a `mirror` + * - We don't have pull or push permissions + * - Pull requests are disabled + * @param repo Repo to check + * @returns `true` if the repository is usable, `false` otherwise + */ +export function usableRepo(repo: Repo): boolean { + if (repo.mirror === true) { + return false; + } + + if (repo.permissions.pull === false || repo.permissions.push === false) { + logger.debug( + `Skipping repository ${repo.full_name} because of missing pull or push permissions`, + ); + return false; + } + + if (repo.has_pull_requests === false) { + logger.debug( + `Skipping repository ${repo.full_name} because pull requests are disabled`, + ); + return false; + } + return true; +} diff --git a/lib/modules/platform/github/graphql.ts b/lib/modules/platform/github/graphql.ts index 09f0addcc06a7c..3931dd47d56257 100644 --- a/lib/modules/platform/github/graphql.ts +++ b/lib/modules/platform/github/graphql.ts @@ -1,8 +1,11 @@ export const repoInfoQuery = ` -query($owner: String!, $name: String!) { +query($owner: String!, $name: String!, $user: String!) { repository(owner: $owner, name: $name) { id isFork + parent { + nameWithOwner + } isArchived nameWithOwner hasIssuesEnabled @@ -17,76 +20,17 @@ query($owner: String!, $name: String!) { oid } } - } -} -`; - -export const closedPrsQuery = ` -query($owner: String!, $name: String!, $count: Int, $cursor: String) { - repository(owner: $owner, name: $name) { - pullRequests( - states: [CLOSED, MERGED], - orderBy: { - field: UPDATED_AT, - direction: DESC - }, - first: $count, - after: $cursor + issues( + orderBy: { field: UPDATED_AT, direction: DESC }, + filterBy: { createdBy: $user }, + first: 5 ) { - pageInfo { - endCursor - hasNextPage - } nodes { number state - headRefName title - comments(last: 100) { - nodes { - databaseId - body - } - } - } - } - } -} -`; - -export const openPrsQuery = ` -query($owner: String!, $name: String!, $count: Int, $cursor: String) { - repository(owner: $owner, name: $name) { - pullRequests( - states: [OPEN], - orderBy: { - field: UPDATED_AT, - direction: DESC - }, - first: $count, - after: $cursor - ) { - pageInfo { - endCursor - hasNextPage - } - nodes { - number - headRefName - baseRefName - title - labels(last: 100) { - nodes { - name - } - } - assignees { - totalCount - } - reviewRequests { - totalCount - } body + updatedAt } } } @@ -117,6 +61,7 @@ query( state title body + updatedAt } } } diff --git a/lib/modules/platform/github/index.spec.ts b/lib/modules/platform/github/index.spec.ts index 11b8efe42f3857..4c1d2a51d0bbbb 100644 --- a/lib/modules/platform/github/index.spec.ts +++ b/lib/modules/platform/github/index.spec.ts @@ -7,6 +7,8 @@ import { PLATFORM_RATE_LIMIT_EXCEEDED, PLATFORM_UNKNOWN_ERROR, REPOSITORY_CANNOT_FORK, + REPOSITORY_FORKED, + REPOSITORY_FORK_MISSING, REPOSITORY_NOT_FOUND, REPOSITORY_RENAMED, } from '../../../constants/error-messages'; @@ -17,7 +19,12 @@ import * as _hostRules from '../../../util/host-rules'; import { setBaseUrl } from '../../../util/http/github'; import { toBase64 } from '../../../util/string'; import { hashBody } from '../pr-body'; -import type { CreatePRConfig, RepoParams, UpdatePrConfig } from '../types'; +import type { + CreatePRConfig, + ReattemptPlatformAutomergeConfig, + RepoParams, + UpdatePrConfig, +} from '../types'; import * as branch from './branch'; import type { ApiPageCache, GhRestPr } from './types'; import * as github from '.'; @@ -483,6 +490,7 @@ describe('modules/platform/github/index', () => { forkExisted: boolean, forkResult = 200, forkDefaultBranch = 'master', + isFork = false, ): void { scope // repo info @@ -490,7 +498,7 @@ describe('modules/platform/github/index', () => { .reply(200, { data: { repository: { - isFork: false, + isFork, isArchived: false, nameWithOwner: repository, hasIssuesEnabled: true, @@ -505,10 +513,10 @@ describe('modules/platform/github/index', () => { }, }, }, - }) - // getForks - .get(`/repos/${repository}/forks?per_page=100`) - .reply( + }); + + if (!isFork) { + scope.get(`/repos/${repository}/forks?per_page=100`).reply( forkResult, forkExisted ? [ @@ -520,6 +528,7 @@ describe('modules/platform/github/index', () => { ] : [], ); + } } describe('initRepo', () => { @@ -543,10 +552,40 @@ describe('modules/platform/github/index', () => { const config = await github.initRepo({ repository: 'some/repo', forkToken: 'true', + forkCreation: true, }); expect(config).toMatchSnapshot(); }); + it('should throw if fork needed but forkCreation=false', async () => { + const scope = httpMock.scope(githubApiHost); + forkInitRepoMock(scope, 'some/repo', false); + scope.get('/user').reply(200, { + login: 'forked', + }); + await expect( + github.initRepo({ + repository: 'some/repo', + forkToken: 'true', + forkCreation: false, + }), + ).rejects.toThrow(REPOSITORY_FORK_MISSING); + }); + + it('throws if the repo is a fork', async () => { + const repo = 'some/repo'; + const branch = 'master'; + const scope = httpMock.scope(githubApiHost); + forkInitRepoMock(scope, repo, false, 200, branch, true); + await expect( + github.initRepo({ + repository: 'some/repo', + forkToken: 'true', + forkCreation: true, + }), + ).rejects.toThrow(REPOSITORY_FORKED); + }); + it('throws when cannot fork due to username error', async () => { const repo = 'some/repo'; const branch = 'master'; @@ -557,6 +596,7 @@ describe('modules/platform/github/index', () => { github.initRepo({ repository: 'some/repo', forkToken: 'true', + forkCreation: true, }), ).rejects.toThrow(REPOSITORY_CANNOT_FORK); }); @@ -569,6 +609,7 @@ describe('modules/platform/github/index', () => { github.initRepo({ repository: 'some/repo', forkToken: 'true', + forkCreation: true, }), ).rejects.toThrow(REPOSITORY_CANNOT_FORK); }); @@ -581,6 +622,7 @@ describe('modules/platform/github/index', () => { github.initRepo({ repository: 'some/repo', forkToken: 'true', + forkCreation: true, }), ).rejects.toThrow(REPOSITORY_CANNOT_FORK); }); @@ -597,6 +639,7 @@ describe('modules/platform/github/index', () => { github.initRepo({ repository: 'some/repo', forkToken: 'true', + forkCreation: true, forkOrg: 'forked', }), ).rejects.toThrow(REPOSITORY_CANNOT_FORK); @@ -609,6 +652,7 @@ describe('modules/platform/github/index', () => { const config = await github.initRepo({ repository: 'some/repo', forkToken: 'true', + forkCreation: true, forkOrg: 'forked', }); expect(config).toMatchSnapshot(); @@ -626,6 +670,7 @@ describe('modules/platform/github/index', () => { const config = await github.initRepo({ repository: 'some/repo', forkToken: 'true', + forkCreation: true, }); expect(config).toMatchSnapshot(); }); @@ -1592,11 +1637,15 @@ describe('modules/platform/github/index', () => { number: 2, state: 'open', title: 'title-2', + body: 'body-2', + updatedAt: '2022-01-01T00:00:00Z', }, { number: 1, state: 'open', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, ], }, @@ -1625,11 +1674,15 @@ describe('modules/platform/github/index', () => { number: 2, state: 'open', title: 'title-2', + body: 'body-2', + updatedAt: '2022-01-01T00:00:00Z', }, { number: 1, state: 'open', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, ], }, @@ -1637,9 +1690,21 @@ describe('modules/platform/github/index', () => { }, }) .get('/repos/undefined/issues/2') - .reply(200, { body: 'new-content' }); + .reply(200, { + number: 2, + state: 'open', + title: 'title-2', + body: 'new-content', + updated_at: '2023-01-01T00:00:00Z', + }); const res = await github.findIssue('title-2'); - expect(res).not.toBeNull(); + expect(res).toEqual({ + number: 2, + state: 'open', + title: 'title-2', + body: 'new-content', + lastModified: '2023-01-01T00:00:00Z', + }); }); }); @@ -1664,11 +1729,15 @@ describe('modules/platform/github/index', () => { number: 2, state: 'open', title: 'title-2', + body: 'body-2', + updatedAt: '2022-01-01T00:00:00Z', }, { number: 1, state: 'open', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, ], }, @@ -1676,7 +1745,13 @@ describe('modules/platform/github/index', () => { }, }) .post('/repos/some/repo/issues') - .reply(200); + .reply(200, { + number: 3, + state: 'open', + title: 'new-title', + body: 'new-content', + updated_at: '2023-01-01T00:00:00Z', + }); const res = await github.ensureIssue({ title: 'new-title', body: 'new-content', @@ -1704,11 +1779,15 @@ describe('modules/platform/github/index', () => { number: 2, state: 'open', title: 'title-2', + body: 'body-2', + updatedAt: '2022-01-01T00:00:00Z', }, { number: 1, state: 'closed', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, ], }, @@ -1742,11 +1821,15 @@ describe('modules/platform/github/index', () => { number: 2, state: 'open', title: 'title-2', + body: 'body-2', + updatedAt: '2022-01-01T00:00:00Z', }, { number: 1, state: 'closed', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, ], }, @@ -1783,7 +1866,13 @@ describe('modules/platform/github/index', () => { }, }) .post('/repos/some/repo/issues') - .reply(200); + .reply(200, { + number: 3, + state: 'open', + title: 'new-title', + body: 'new-content', + updated_at: '2023-01-01T00:00:00Z', + }); const res = await github.ensureIssue({ title: 'new-title', body: 'new-content', @@ -1812,16 +1901,22 @@ describe('modules/platform/github/index', () => { number: 3, state: 'open', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, { number: 2, state: 'open', title: 'title-2', + body: 'body-2', + updatedAt: '2022-01-01T00:00:00Z', }, { number: 1, state: 'closed', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, ], }, @@ -1859,11 +1954,15 @@ describe('modules/platform/github/index', () => { number: 2, state: 'open', title: 'title-2', + body: 'body-2', + updatedAt: '2022-01-01T00:00:00Z', }, { number: 1, state: 'open', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, ], }, @@ -1871,9 +1970,21 @@ describe('modules/platform/github/index', () => { }, }) .get('/repos/some/repo/issues/2') - .reply(200, { body: 'new-content' }) + .reply(200, { + number: 2, + state: 'open', + title: 'title-2', + body: 'new-content', + updated_at: '2023-01-01T00:00:00Z', + }) .patch('/repos/some/repo/issues/2') - .reply(200); + .reply(200, { + number: 2, + state: 'open', + title: 'title-2', + body: 'newer-content', + updated_at: '2023-01-01T00:00:00Z', + }); const res = await github.ensureIssue({ title: 'title-3', reuseTitle: 'title-2', @@ -1902,11 +2013,15 @@ describe('modules/platform/github/index', () => { number: 2, state: 'open', title: 'title-2', + body: 'body-2', + updatedAt: '2022-01-01T00:00:00Z', }, { number: 1, state: 'open', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, ], }, @@ -1914,9 +2029,21 @@ describe('modules/platform/github/index', () => { }, }) .get('/repos/some/repo/issues/2') - .reply(200, { body: 'new-content' }) + .reply(200, { + number: 2, + state: 'open', + title: 'title-2', + body: 'new-content', + updated_at: '2023-01-01T00:00:00Z', + }) .patch('/repos/some/repo/issues/2') - .reply(200); + .reply(200, { + number: 2, + state: 'open', + title: 'title-2', + body: 'newer-content', + updated_at: '2023-01-01T00:00:00Z', + }); const res = await github.ensureIssue({ title: 'title-3', reuseTitle: 'title-2', @@ -1946,11 +2073,15 @@ describe('modules/platform/github/index', () => { number: 2, state: 'open', title: 'title-2', + body: 'newer-content', + updatedAt: '2022-01-01T00:00:00Z', }, { number: 1, state: 'open', title: 'title-1', + body: 'new-content', + updatedAt: '2021-01-01T00:00:00Z', }, ], }, @@ -1986,11 +2117,15 @@ describe('modules/platform/github/index', () => { number: 2, state: 'open', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, { number: 1, state: 'open', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, ], }, @@ -1998,9 +2133,7 @@ describe('modules/platform/github/index', () => { }, }) .patch('/repos/some/repo/issues/1') - .reply(200) - .get('/repos/some/repo/issues/2') - .reply(200, { body: 'newer-content' }); + .reply(200); const res = await github.ensureIssue({ title: 'title-1', body: 'newer-content', @@ -2028,6 +2161,8 @@ describe('modules/platform/github/index', () => { number: 2, state: 'close', title: 'title-2', + body: 'body-2', + updatedAt: '2022-01-01T00:00:00Z', }, ], }, @@ -2035,9 +2170,21 @@ describe('modules/platform/github/index', () => { }, }) .get('/repos/some/repo/issues/2') - .reply(200, { body: 'new-content' }) + .reply(200, { + number: 2, + state: 'closed', + title: 'title-2', + body: 'new-content', + updated_at: '2023-01-01T00:00:00Z', + }) .post('/repos/some/repo/issues') - .reply(200); + .reply(200, { + number: 3, + state: 'open', + title: 'title-2', + body: 'new-content', + updated_at: '2023-01-01T00:00:00Z', + }); const res = await github.ensureIssue({ title: 'title-2', body: 'new-content', @@ -2067,6 +2214,8 @@ describe('modules/platform/github/index', () => { number: 2, state: 'open', title: 'title-2', + body: 'body-2', + updatedAt: '2022-01-01T00:00:00Z', }, ], }, @@ -2074,7 +2223,13 @@ describe('modules/platform/github/index', () => { }, }) .get('/repos/some/repo/issues/2') - .reply(200, { body: 'new-content' }); + .reply(200, { + number: 2, + state: 'open', + title: 'title-2', + body: 'new-content', + updated_at: '2023-01-01T00:00:00Z', + }); const res = await github.ensureIssue({ title: 'title-2', body: 'new-content', @@ -2104,11 +2259,15 @@ describe('modules/platform/github/index', () => { number: 2, state: 'open', title: 'title-2', + body: 'body-2', + updatedAt: '2022-01-01T00:00:00Z', }, { number: 1, state: 'open', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, ], }, @@ -2116,7 +2275,13 @@ describe('modules/platform/github/index', () => { }, }) .patch('/repos/undefined/issues/2') - .reply(200); + .reply(200, { + number: 2, + state: 'closed', + title: 'title-2', + body: 'new-content', + updated_at: '2023-01-01T00:00:00Z', + }); await expect(github.ensureIssueClosing('title-2')).toResolve(); }); }); @@ -2135,7 +2300,13 @@ describe('modules/platform/github/index', () => { it('should add the given assignees to the issue', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); - scope.post('/repos/some/repo/issues/42/assignees').reply(200); + scope.post('/repos/some/repo/issues/42/assignees').reply(200, { + number: 42, + state: 'open', + title: 'title-42', + body: 'body-42', + updated_at: '2023-01-01T00:00:00Z', + }); await github.initRepo({ repository: 'some/repo' }); await expect( github.addAssignees(42, ['someuser', 'someotheruser']), @@ -2552,6 +2723,7 @@ describe('modules/platform/github/index', () => { await github.initRepo({ repository: 'some/repo', forkToken: 'true', + forkCreation: true, }); }); @@ -2868,7 +3040,13 @@ describe('modules/platform/github/index', () => { }); scope .patch('/repos/some/repo/issues/123', (body) => body.milestone === 1) - .reply(200, {}); + .reply(200, { + number: 123, + state: 'open', + title: 'bump someDep to v2', + body: 'many informations about someDep', + updated_at: '2023-01-01T00:00:00Z', + }); await github.initRepo({ repository: 'some/repo' }); const pr = await github.createPr({ targetBranch: 'main', @@ -3195,6 +3373,165 @@ describe('modules/platform/github/index', () => { await expect(github.updatePr(pr)).toResolve(); }); + + it('should add and remove labels', async () => { + const pr: UpdatePrConfig = { + number: 1234, + prTitle: 'The New Title', + prBody: 'Hello world again', + state: 'closed', + targetBranch: 'new_base', + addLabels: ['new_label'], + removeLabels: ['old_label'], + }; + const scope = httpMock.scope(githubApiHost); + initRepoMock(scope, 'some/repo'); + await github.initRepo({ repository: 'some/repo' }); + scope + .patch('/repos/some/repo/pulls/1234') + .reply(200, { + number: 91, + base: { sha: '1234' }, + head: { ref: 'somebranch', repo: { full_name: 'some/repo' } }, + state: 'open', + title: 'old title', + updated_at: '01-09-2022', + }) + .post('/repos/some/repo/issues/1234/labels') + .reply(200, pr) + .delete('/repos/some/repo/issues/1234/labels/old_label') + .reply(200, pr); + + await expect(github.updatePr(pr)).toResolve(); + expect(logger.logger.debug).toHaveBeenCalledWith( + `Adding labels 'new_label' to #1234`, + ); + expect(logger.logger.debug).toHaveBeenCalledWith( + `Deleting label old_label from #1234`, + ); + }); + }); + + describe('reattemptPlatformAutomerge(number, platformOptions)', () => { + const getPrListResp = [ + { + number: 1234, + base: { sha: '1234' }, + head: { ref: 'somebranch', repo: { full_name: 'some/repo' } }, + state: 'open', + title: 'Some PR', + }, + ]; + const getPrResp = { + number: 123, + node_id: 'abcd', + head: { repo: { full_name: 'some/repo' } }, + }; + + const graphqlAutomergeResp = { + data: { + enablePullRequestAutoMerge: { + pullRequest: { + number: 123, + }, + }, + }, + }; + + const pr: ReattemptPlatformAutomergeConfig = { + number: 123, + platformOptions: { usePlatformAutomerge: true }, + }; + + const mockScope = async (repoOpts: any = {}): Promise => { + const scope = httpMock.scope(githubApiHost); + initRepoMock(scope, 'some/repo', repoOpts); + scope + .get( + '/repos/some/repo/pulls?per_page=100&state=all&sort=updated&direction=desc&page=1', + ) + .reply(200, getPrListResp); + scope.get('/repos/some/repo/pulls/123').reply(200, getPrResp); + await github.initRepo({ repository: 'some/repo' }); + return scope; + }; + + const graphqlGetRepo = { + method: 'POST', + url: 'https://api.github.com/graphql', + graphql: { query: { repository: {} } }, + }; + + const restGetPrList = { + method: 'GET', + url: 'https://api.github.com/repos/some/repo/pulls?per_page=100&state=all&sort=updated&direction=desc&page=1', + }; + + const restGetPr = { + method: 'GET', + url: 'https://api.github.com/repos/some/repo/pulls/123', + }; + + const graphqlAutomerge = { + method: 'POST', + url: 'https://api.github.com/graphql', + graphql: { + mutation: { + __vars: { + $pullRequestId: 'ID!', + $mergeMethod: 'PullRequestMergeMethod!', + }, + enablePullRequestAutoMerge: { + __args: { + input: { + pullRequestId: '$pullRequestId', + mergeMethod: '$mergeMethod', + }, + }, + }, + }, + variables: { + pullRequestId: 'abcd', + mergeMethod: 'REBASE', + }, + }, + }; + + it('should set automatic merge', async () => { + const scope = await mockScope(); + scope.post('/graphql').reply(200, graphqlAutomergeResp); + + await expect(github.reattemptPlatformAutomerge(pr)).toResolve(); + + expect(logger.logger.debug).toHaveBeenLastCalledWith( + 'PR platform automerge re-attempted...prNo: 123', + ); + + expect(httpMock.getTrace()).toMatchObject([ + graphqlGetRepo, + restGetPrList, + restGetPr, + graphqlAutomerge, + ]); + }); + + it('handles unknown error', async () => { + const scope = httpMock.scope(githubApiHost); + initRepoMock(scope, 'some/repo'); + await github.initRepo({ repository: 'some/repo' }); + scope + .get( + '/repos/some/repo/pulls?per_page=100&state=all&sort=updated&direction=desc&page=1', + ) + .replyWithError('unknown error'); + + await expect(github.reattemptPlatformAutomerge(pr)).toResolve(); + + expect(logger.logger.warn).toHaveBeenCalledWith( + { err: new Error('external-host-error') }, + 'Error re-attempting PR platform automerge', + ); + }); }); describe('mergePr(prNo)', () => { diff --git a/lib/modules/platform/github/index.ts b/lib/modules/platform/github/index.ts index c34abf4abcbf14..a0873b7d0c09b6 100644 --- a/lib/modules/platform/github/index.ts +++ b/lib/modules/platform/github/index.ts @@ -16,6 +16,8 @@ import { REPOSITORY_DISABLED, REPOSITORY_EMPTY, REPOSITORY_FORKED, + REPOSITORY_FORK_MISSING, + REPOSITORY_FORK_MODE_FORKED, REPOSITORY_NOT_FOUND, REPOSITORY_RENAMED, } from '../../../constants/error-messages'; @@ -33,12 +35,10 @@ import type { LongCommitSha, } from '../../../util/git/types'; import * as hostRules from '../../../util/host-rules'; +import { repoCacheProvider } from '../../../util/http/cache/repository-http-cache-provider'; import * as githubHttp from '../../../util/http/github'; import type { GithubHttpOptions } from '../../../util/http/github'; -import type { - HttpResponse, - InternalHttpOptions, -} from '../../../util/http/types'; +import type { HttpResponse } from '../../../util/http/types'; import { coerceObject } from '../../../util/object'; import { regEx } from '../../../util/regex'; import { sanitize } from '../../../util/sanitize'; @@ -54,11 +54,11 @@ import type { EnsureIssueConfig, EnsureIssueResult, FindPRConfig, - Issue, MergePRConfig, PlatformParams, PlatformPrOptions, PlatformResult, + ReattemptPlatformAutomergeConfig, RepoParams, RepoResult, UpdatePrConfig, @@ -73,6 +73,7 @@ import { repoInfoQuery, vulnerabilityAlertsQuery, } from './graphql'; +import { GithubIssueCache, GithubIssue as Issue } from './issue'; import { massageMarkdownLinks } from './massage-markdown-links'; import { getPrCache, updatePrCache } from './pr'; import type { @@ -309,7 +310,7 @@ async function getBranchProtection( } const res = await githubApi.getJson( `repos/${config.repository}/branches/${escapeHash(branchName)}/protection`, - { repoCache: true }, + { cacheProvider: repoCacheProvider }, ); return res.body; } @@ -320,11 +321,14 @@ export async function getRawFile( branchOrTag?: string, ): Promise { const repo = repoName ?? config.repository; + + // only use cache for the same org + const httpOptions: GithubHttpOptions = {}; const isSameOrg = repo?.split('/')?.[0] === config.repositoryOwner; - const httpOptions: InternalHttpOptions = { - // Only cache response if it's from the same org - repoCache: isSameOrg, - }; + if (isSameOrg) { + httpOptions.cacheProvider = repoCacheProvider; + } + let url = `repos/${repo}/contents/${fileName}`; if (branchOrTag) { url += `?ref=` + branchOrTag; @@ -433,6 +437,7 @@ export async function createFork( export async function initRepo({ endpoint, repository, + forkCreation, forkOrg, forkToken, renovateUsername, @@ -456,6 +461,7 @@ export async function initRepo({ const opts = hostRules.find({ hostType: 'github', url: platformConfig.endpoint, + readOnly: true, }); config.renovateUsername = renovateUsername; [config.repositoryOwner, config.repositoryName] = repository.split('/'); @@ -492,7 +498,9 @@ export async function initRepo({ variables: { owner: config.repositoryOwner, name: config.repositoryName, + user: renovateUsername, }, + readOnly: true, }); if (res?.errors) { @@ -552,6 +560,11 @@ export async function initRepo({ config.autoMergeAllowed = repo.autoMergeAllowed; config.hasIssuesEnabled = repo.hasIssuesEnabled; config.hasVulnerabilityAlertsEnabled = repo.hasVulnerabilityAlertsEnabled; + + const recentIssues = Issue.array() + .catch([]) + .parse(res?.data?.repository?.issues?.nodes); + GithubIssueCache.addIssuesToReconcile(recentIssues); } catch (err) /* istanbul ignore next */ { logger.debug({ err }, 'Caught initRepo error'); if ( @@ -570,6 +583,9 @@ export async function initRepo({ if (err.message.startsWith('Repository access blocked')) { throw new Error(REPOSITORY_BLOCKED); } + if (err.message === REPOSITORY_FORK_MODE_FORKED) { + throw err; + } if (err.message === REPOSITORY_FORKED) { throw err; } @@ -583,11 +599,19 @@ export async function initRepo({ throw err; } // This shouldn't be necessary, but occasional strange errors happened until it was added - config.issueList = null; config.prList = null; if (forkToken) { logger.debug('Bot is in fork mode'); + if (repo.isFork) { + logger.debug( + `Forked repos cannot be processed when running with a forkToken, so this repo will be skipped`, + ); + logger.debug( + `Parent repo for this forked repo is ${repo.parent?.nameWithOwner}`, + ); + throw new Error(REPOSITORY_FORKED); + } config.forkOrg = forkOrg; config.forkToken = forkToken; // save parent name then delete @@ -669,10 +693,13 @@ export async function initRepo({ } throw new ExternalHostError(err); } - } else { + } else if (forkCreation) { logger.debug('Forked repo is not found - attempting to create it'); forkedRepo = await createFork(forkToken, repository, forkOrg); config.repository = forkedRepo.full_name; + } else { + logger.debug('Forked repo is not found and forkCreation is disabled'); + throw new Error(REPOSITORY_FORK_MISSING); } } @@ -822,11 +849,11 @@ export async function findPr({ if (includeOtherAuthors) { const repo = config.parentRepo ?? config.repository; // PR might have been created by anyone, so don't use the cached Renovate PR list - const response = await githubApi.getJson( + const { body: prList } = await githubApi.getJson( `repos/${repo}/pulls?head=${repo}:${branchName}&state=open`, + { cacheProvider: repoCacheProvider }, ); - const { body: prList } = response; if (!prList.length) { logger.debug(`No PR found for branch ${branchName}`); return null; @@ -974,15 +1001,15 @@ async function getStatus( branchName: string, useCache = true, ): Promise { - const commitStatusUrl = `repos/${config.repository}/commits/${escapeHash( - branchName, - )}/status`; + const branch = escapeHash(branchName); + const url = `repos/${config.repository}/commits/${branch}/status`; - return ( - await githubApi.getJson(commitStatusUrl, { - memCache: useCache, - }) - ).body; + const { body: status } = await githubApi.getJson(url, { + memCache: useCache, + cacheProvider: repoCacheProvider, + }); + + return status; } // Returns the combined status for a branch. @@ -1179,9 +1206,8 @@ export async function setBranchStatus({ // Issue -/* istanbul ignore next */ async function getIssues(): Promise { - const result = await githubApi.queryRepoField( + const result = await githubApi.queryRepoField( getIssuesQuery, 'issues', { @@ -1190,14 +1216,12 @@ async function getIssues(): Promise { name: config.repositoryName, user: config.renovateUsername, }, + readOnly: true, }, ); logger.debug(`Retrieved ${result.length} issues`); - return result.map((issue) => ({ - ...issue, - state: issue.state?.toLowerCase(), - })); + return Issue.array().parse(result); } export async function getIssueList(): Promise { @@ -1205,32 +1229,31 @@ export async function getIssueList(): Promise { if (config.hasIssuesEnabled === false) { return []; } - if (!config.issueList) { + let issueList = GithubIssueCache.getIssues(); + if (!issueList) { logger.debug('Retrieving issueList'); - config.issueList = await getIssues(); + issueList = await getIssues(); + GithubIssueCache.setIssues(issueList); } - return config.issueList; + return issueList; } -export async function getIssue( - number: number, - useCache = true, -): Promise { +export async function getIssue(number: number): Promise { // istanbul ignore if if (config.hasIssuesEnabled === false) { return null; } try { - const issueBody = ( - await githubApi.getJson<{ body: string }>( - `repos/${config.parentRepo ?? config.repository}/issues/${number}`, - { memCache: useCache, repoCache: true }, - ) - ).body.body; - return { - number, - body: issueBody, - }; + const repo = config.parentRepo ?? config.repository; + const { body: issue } = await githubApi.getJson( + `repos/${repo}/issues/${number}`, + { + cacheProvider: repoCacheProvider, + }, + Issue, + ); + GithubIssueCache.updateIssue(issue); + return issue; } catch (err) /* istanbul ignore next */ { logger.debug({ err, number }, 'Error getting issue'); return null; @@ -1246,18 +1269,18 @@ export async function findIssue(title: string): Promise { return null; } logger.debug(`Found issue ${issue.number}`); - // TODO: can number be required? (#22198) - return getIssue(issue.number!); + return getIssue(issue.number); } async function closeIssue(issueNumber: number): Promise { logger.debug(`closeIssue(${issueNumber})`); - await githubApi.patchJson( - `repos/${config.parentRepo ?? config.repository}/issues/${issueNumber}`, - { - body: { state: 'closed' }, - }, + const repo = config.parentRepo ?? config.repository; + const { body: closedIssue } = await githubApi.patchJson( + `repos/${repo}/issues/${issueNumber}`, + { body: { state: 'closed' } }, + Issue, ); + GithubIssueCache.updateIssue(closedIssue); } export async function ensureIssue({ @@ -1301,21 +1324,21 @@ export async function ensureIssue({ for (const i of issues) { if (i.state === 'open' && i.number !== issue.number) { logger.warn({ issueNo: i.number }, 'Closing duplicate issue'); - // TODO #22198 - await closeIssue(i.number!); + await closeIssue(i.number); } } - const issueBody = ( - await githubApi.getJson<{ body: string }>( - `repos/${config.parentRepo ?? config.repository}/issues/${ - issue.number - }`, - { repoCache: true }, - ) - ).body.body; + + const repo = config.parentRepo ?? config.repository; + const { body: serverIssue } = await githubApi.getJson( + `repos/${repo}/issues/${issue.number}`, + { cacheProvider: repoCacheProvider }, + Issue, + ); + GithubIssueCache.updateIssue(serverIssue); + if ( issue.title === title && - issueBody === body && + serverIssue.body === body && issue.state === 'open' ) { logger.debug('Issue is open and up to date - nothing to do'); @@ -1327,19 +1350,18 @@ export async function ensureIssue({ if (labels) { data.labels = labels; } - await githubApi.patchJson( - `repos/${config.parentRepo ?? config.repository}/issues/${ - issue.number - }`, - { - body: data, - }, + const repo = config.parentRepo ?? config.repository; + const { body: updatedIssue } = await githubApi.patchJson( + `repos/${repo}/issues/${issue.number}`, + { body: data }, + Issue, ); + GithubIssueCache.updateIssue(updatedIssue); logger.debug('Issue updated'); return 'updated'; } } - await githubApi.postJson( + const { body: createdIssue } = await githubApi.postJson( `repos/${config.parentRepo ?? config.repository}/issues`, { body: { @@ -1348,10 +1370,11 @@ export async function ensureIssue({ labels: labels ?? [], }, }, + Issue, ); logger.info('Issue created'); // reset issueList so that it will be fetched again as-needed - config.issueList = null; + GithubIssueCache.updateIssue(createdIssue); return 'created'; } catch (err) /* istanbul ignore next */ { if (err.body?.message?.startsWith('Issues are disabled for this repo')) { @@ -1375,8 +1398,7 @@ export async function ensureIssueClosing(title: string): Promise { const issueList = await getIssueList(); for (const issue of issueList) { if (issue.state === 'open' && issue.title === title) { - // TODO #22198 - await closeIssue(issue.number!); + await closeIssue(issue.number); logger.debug(`Issue closed, issueNo: ${issue.number}`); } } @@ -1397,13 +1419,14 @@ async function tryAddMilestone( }, 'Adding milestone to PR', ); - const repository = config.parentRepo ?? config.repository; try { - await githubApi.patchJson(`repos/${repository}/issues/${issueNo}`, { - body: { - milestone: milestoneNo, - }, - }); + const repo = config.parentRepo ?? config.repository; + const { body: updatedIssue } = await githubApi.patchJson( + `repos/${repo}/issues/${issueNo}`, + { body: { milestone: milestoneNo } }, + Issue, + ); + GithubIssueCache.updateIssue(updatedIssue); } catch (err) { const actualError = err.response?.body || /* istanbul ignore next */ err; logger.warn( @@ -1423,11 +1446,12 @@ export async function addAssignees( ): Promise { logger.debug(`Adding assignees '${assignees.join(', ')}' to #${issueNo}`); const repository = config.parentRepo ?? config.repository; - await githubApi.postJson(`repos/${repository}/issues/${issueNo}/assignees`, { - body: { - assignees, - }, - }); + const { body: updatedIssue } = await githubApi.postJson( + `repos/${repository}/issues/${issueNo}/assignees`, + { body: { assignees } }, + Issue, + ); + GithubIssueCache.updateIssue(updatedIssue); } export async function addReviewers( @@ -1521,15 +1545,13 @@ async function deleteComment(commentId: number): Promise { async function getComments(issueNo: number): Promise { // GET /repos/:owner/:repo/issues/:number/comments logger.debug(`Getting comments for #${issueNo}`); - const url = `repos/${ - config.parentRepo ?? config.repository - }/issues/${issueNo}/comments?per_page=100`; + const repo = config.parentRepo ?? config.repository; + const url = `repos/${repo}/issues/${issueNo}/comments?per_page=100`; try { - const comments = ( - await githubApi.getJson(url, { - paginate: true, - }) - ).body; + const { body: comments } = await githubApi.getJson(url, { + paginate: true, + cacheProvider: repoCacheProvider, + }); logger.debug(`Found ${comments.length} comments`); return comments; } catch (err) /* istanbul ignore next */ { @@ -1748,6 +1770,8 @@ export async function updatePr({ number: prNo, prTitle: title, prBody: rawBody, + addLabels: labelsToAdd, + removeLabels, state, targetBranch, }: UpdatePrConfig): Promise { @@ -1770,7 +1794,19 @@ export async function updatePr({ if (config.forkToken) { options.token = config.forkToken; } + + // Update PR labels try { + if (labelsToAdd) { + await addLabels(prNo, labelsToAdd); + } + + if (removeLabels) { + for (const label of removeLabels) { + await deleteLabel(prNo, label); + } + } + const { body: ghPr } = await githubApi.patchJson( `repos/${config.parentRepo ?? config.repository}/pulls/${prNo}`, options, @@ -1786,6 +1822,22 @@ export async function updatePr({ } } +export async function reattemptPlatformAutomerge({ + number, + platformOptions, +}: ReattemptPlatformAutomergeConfig): Promise { + try { + const result = (await getPr(number))!; + const { node_id } = result; + + await tryPrAutomerge(number, node_id, platformOptions); + + logger.debug(`PR platform automerge re-attempted...prNo: ${number}`); + } catch (err) { + logger.warn({ err }, 'Error re-attempting PR platform automerge'); + } +} + export async function mergePr({ branchName, id: prNo, @@ -1898,6 +1950,7 @@ export function massageMarkdown(input: string): string { .replace(regEx(/]: https:\/\/github\.com\//g), ']: https://togithub.com/') .replace('> ℹ **Note**\n> \n', '> [!NOTE]\n') .replace('> ⚠ **Warning**\n> \n', '> [!WARNING]\n') + .replace('> ⚠️ **Warning**\n> \n', '> [!WARNING]\n') .replace('> ❗ **Important**\n> \n', '> [!IMPORTANT]\n'); return smartTruncate(massagedInput, GitHubMaxPrBodyLen); } @@ -1926,6 +1979,7 @@ export async function getVulnerabilityAlerts(): Promise { variables: { owner: config.repositoryOwner, name: config.repositoryName }, paginate: false, acceptHeader: 'application/vnd.github.vixen-preview+json', + readOnly: true, }); } catch (err) { logger.debug({ err }, 'Error retrieving vulnerability alerts'); diff --git a/lib/modules/platform/github/issue.spec.ts b/lib/modules/platform/github/issue.spec.ts new file mode 100644 index 00000000000000..c2d028b4bdfb02 --- /dev/null +++ b/lib/modules/platform/github/issue.spec.ts @@ -0,0 +1,247 @@ +import * as memCache from '../../../util/cache/memory'; +import { getCache, resetCache } from '../../../util/cache/repository'; +import { GithubIssue, GithubIssueCache } from './issue'; + +describe('modules/platform/github/issue', () => { + describe('GithubIssueCache', () => { + let cache = getCache(); + + beforeEach(() => { + resetCache(); + cache = getCache(); + memCache.init(); + }); + + it('returns null for empty cache', () => { + expect(GithubIssueCache.getIssues()).toBeNull(); + }); + + it('stores issues to the cache', () => { + const issues: GithubIssue[] = [ + { + number: 1, + body: 'body-1', + state: 'open', + title: 'title-1', + lastModified: '2020-01-01T00:00:00.000Z', + }, + { + number: 2, + body: 'body-2', + state: 'closed', + title: 'title-2', + lastModified: '2020-01-02T00:00:00.000Z', + }, + ]; + + GithubIssueCache.setIssues(issues); + + expect(cache).toEqual({ + platform: { + github: { + issuesCache: { + '1': { + number: 1, + body: 'body-1', + state: 'open', + title: 'title-1', + lastModified: '2020-01-01T00:00:00.000Z', + }, + '2': { + number: 2, + body: 'body-2', + state: 'closed', + title: 'title-2', + lastModified: '2020-01-02T00:00:00.000Z', + }, + }, + }, + }, + }); + }); + + it('returns issues from the cache in the correct order', () => { + cache.platform = { + github: { + issuesCache: { + '2': { + number: 2, + body: 'body-2', + state: 'closed', + title: 'title-2', + lastModified: '2020-01-02T00:00:00.000Z', + }, + '1': { + number: 1, + body: 'body-1', + state: 'open', + title: 'title-1', + lastModified: '2020-01-01T00:00:00.000Z', + }, + '3': { + number: 3, + body: 'body-3', + state: 'closed', + title: 'title-3', + lastModified: '2020-01-03T00:00:00.000Z', + }, + }, + }, + }; + + const res = GithubIssueCache.getIssues(); + + expect(res).toEqual([ + { + number: 3, + body: 'body-3', + state: 'closed', + title: 'title-3', + lastModified: '2020-01-03T00:00:00.000Z', + }, + { + number: 2, + body: 'body-2', + state: 'closed', + title: 'title-2', + lastModified: '2020-01-02T00:00:00.000Z', + }, + { + number: 1, + body: 'body-1', + state: 'open', + title: 'title-1', + lastModified: '2020-01-01T00:00:00.000Z', + }, + ]); + }); + + it('updates particular issue in the cache', () => { + cache.platform = { + github: { + issuesCache: { + '1': { + number: 1, + body: 'body-1', + state: 'open', + title: 'title-1', + lastModified: '2020-01-01T00:00:00.000Z', + }, + }, + }, + }; + + const issue: GithubIssue = { + number: 1, + body: 'new-body-1', + state: 'closed', + title: 'new-title-1', + lastModified: '2020-01-02T00:00:00.000Z', + }; + + GithubIssueCache.updateIssue(issue); + + expect(cache).toEqual({ + platform: { + github: { + issuesCache: { + '1': { + number: 1, + body: 'new-body-1', + state: 'closed', + title: 'new-title-1', + lastModified: '2020-01-02T00:00:00.000Z', + }, + }, + }, + }, + }); + }); + + it('reconciles cache', () => { + cache.platform = { + github: { + issuesCache: { + '1': { + number: 1, + body: 'body-1', + state: 'open', + title: 'title-1', + lastModified: '2020-01-01T00:00:00.000Z', + }, + '2': { + number: 2, + body: 'body-2', + state: 'closed', + title: 'title-2', + lastModified: '2020-01-02T00:00:00.000Z', + }, + }, + }, + }; + + GithubIssueCache.addIssuesToReconcile([ + { + number: 1, + body: 'new-body-1', + state: 'open', + title: 'new-title-1', + lastModified: '2020-01-04T00:00:00.000Z', + }, + { + number: 2, + body: 'body-2', + state: 'closed', + title: 'title-2', + lastModified: '2020-01-02T00:00:00.000Z', + }, + ]); + const res = GithubIssueCache.getIssues(); + + expect(res).toEqual([ + { + number: 1, + body: 'new-body-1', + state: 'open', + title: 'new-title-1', + lastModified: '2020-01-04T00:00:00.000Z', + }, + { + number: 2, + body: 'body-2', + state: 'closed', + title: 'title-2', + lastModified: '2020-01-02T00:00:00.000Z', + }, + ]); + }); + + it('resets cache during failed reconciliation', () => { + cache.platform = { + github: { + issuesCache: { + '1': { + number: 1, + body: 'body-1', + state: 'open', + title: 'title-1', + lastModified: '2020-01-01T00:00:00.000Z', + }, + '2': { + number: 2, + body: 'body-2', + state: 'closed', + title: 'title-2', + lastModified: '2020-01-02T00:00:00.000Z', + }, + }, + }, + }; + + GithubIssueCache.addIssuesToReconcile([]); + const res = GithubIssueCache.getIssues(); + + expect(res).toBeNull(); + }); + }); +}); diff --git a/lib/modules/platform/github/issue.ts b/lib/modules/platform/github/issue.ts new file mode 100644 index 00000000000000..1cc85151777233 --- /dev/null +++ b/lib/modules/platform/github/issue.ts @@ -0,0 +1,139 @@ +import { DateTime } from 'luxon'; +import { z } from 'zod'; +import { logger } from '../../../logger'; +import * as memCache from '../../../util/cache/memory'; +import { getCache } from '../../../util/cache/repository'; + +const GithubIssueBase = z.object({ + number: z.number(), + state: z.string().transform((val) => val.toLowerCase()), + title: z.string(), + body: z.string(), +}); + +const GithubGraphqlIssue = GithubIssueBase.extend({ + updatedAt: z.string(), +}).transform((issue) => { + const lastModified = issue.updatedAt; + const { number, state, title, body } = issue; + return { number, state, title, body, lastModified }; +}); + +const GithubRestIssue = GithubIssueBase.extend({ + updated_at: z.string(), +}).transform((issue) => { + const lastModified = issue.updated_at; + const { number, state, title, body } = issue; + return { number, state, title, body, lastModified }; +}); + +export const GithubIssue = z.union([GithubGraphqlIssue, GithubRestIssue]); +export type GithubIssue = z.infer; + +type CacheData = Record; + +export class GithubIssueCache { + private static reset(cacheData: CacheData | null): void { + memCache.set('github-issues-reconcile-queue', null); + const repoCache = getCache(); + repoCache.platform ??= {}; + repoCache.platform.github ??= {}; + if (cacheData) { + repoCache.platform.github.issuesCache = cacheData; + } else { + delete repoCache.platform.github.issuesCache; + } + } + + private static get data(): CacheData | null { + let cacheData: CacheData | undefined | null = getCache().platform?.github + ?.issuesCache as CacheData | undefined; + if (!cacheData) { + return null; + } + + cacheData = this.reconcile(cacheData); + return cacheData; + } + + static getIssues(): GithubIssue[] | null { + const cacheData = this.data; + if (!cacheData) { + return null; + } + + const sortedResult = Object.values(cacheData).sort( + ({ lastModified: a }, { lastModified: b }) => + DateTime.fromISO(b).toMillis() - DateTime.fromISO(a).toMillis(), + ); + + return sortedResult; + } + + static setIssues(issues: GithubIssue[]): void { + const cacheData: CacheData = {}; + for (const issue of issues) { + cacheData[issue.number] = issue; + } + this.reset(cacheData); + } + + static updateIssue(issue: GithubIssue): void { + const cacheData = this.data; + if (cacheData) { + cacheData[issue.number] = issue; + } + } + + /** + * At the moment of repo initialization, repository cache is not available. + * What we can do is to store issues for later reconciliation. + */ + static addIssuesToReconcile(issues: GithubIssue[] | undefined): void { + memCache.set('github-issues-reconcile-queue', issues); + } + + private static reconcile(cacheData: CacheData): CacheData | null { + const issuesToReconcile = memCache.get( + 'github-issues-reconcile-queue', + ); + if (!issuesToReconcile) { + return cacheData; + } + + let isReconciled = false; + + for (const issue of issuesToReconcile) { + const cachedIssue = cacheData[issue.number]; + + // If we reached the item which is already in the cache, + // it means sync is done. + if ( + cachedIssue && + cachedIssue.number === issue.number && + cachedIssue.lastModified === issue.lastModified + ) { + isReconciled = true; + break; + } + + cacheData[issue.number] = issue; + } + + // If we've just iterated over all the items in the cache, + // it means sync is also done. + if (issuesToReconcile.length >= Object.keys(cacheData).length) { + isReconciled = true; + } + + if (!isReconciled) { + logger.debug('Issues cache: reset'); + this.reset(null); + return null; + } + + logger.debug('Issues cache: synced'); + this.reset(cacheData); + return cacheData; + } +} diff --git a/lib/modules/platform/github/pr.ts b/lib/modules/platform/github/pr.ts index 8451749b3c889d..66b37aa3426fcc 100644 --- a/lib/modules/platform/github/pr.ts +++ b/lib/modules/platform/github/pr.ts @@ -2,6 +2,7 @@ import is from '@sindresorhus/is'; import { logger } from '../../../logger'; import { ExternalHostError } from '../../../types/errors/external-host-error'; import { getCache } from '../../../util/cache/repository'; +import { repoCacheProvider } from '../../../util/http/cache/repository-http-cache-provider'; import type { GithubHttp, GithubHttpOptions } from '../../../util/http/github'; import { parseLinkHeader } from '../../../util/url'; import { ApiCache } from './api-cache'; @@ -12,7 +13,6 @@ function getPrApiCache(): ApiCache { const repoCache = getCache(); repoCache.platform ??= {}; repoCache.platform.github ??= {}; - delete repoCache.platform.github.prCache; repoCache.platform.github.pullRequestsCache ??= { items: {} }; const prApiCache = new ApiCache( repoCache.platform.github.pullRequestsCache as ApiPageCache, @@ -65,7 +65,7 @@ export async function getPrCache( while (needNextPageFetch && needNextPageSync) { const opts: GithubHttpOptions = { paginate: false }; if (pageIdx === 1) { - opts.repoCache = true; + opts.cacheProvider = repoCacheProvider; if (isInitial) { // Speed up initial fetch opts.paginate = true; diff --git a/lib/modules/platform/github/readme.md b/lib/modules/platform/github/readme.md index a12ff89f11c617..996e21804244f1 100644 --- a/lib/modules/platform/github/readme.md +++ b/lib/modules/platform/github/readme.md @@ -2,8 +2,11 @@ ## Authentication -First, [create a classic Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token#creating-a-personal-access-token-classic) for the bot account, select `repo` scope. -Fine-grained Personal Access Tokens do not support the GitHub GraphQL API and cannot be used with Renovate. +First, create a [fine-grained](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token) _or_ a [classic](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token#creating-a-personal-access-token-classic) PAT. +The PAT must have the `repo` scope. +If you want Renovate to also update your GitHub Action files, you must grant the `workflow` scope. + +Read the [GitHub Docs, about Personal Access Tokens](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#about-personal-access-tokens) to learn more about PATs. Let Renovate use your PAT by doing _one_ of the following: @@ -25,6 +28,26 @@ You can choose where you want to set `endpoint`: If you're self-hosting Renovate on GitHub.com with GitHub Actions in forking mode, and want Renovate to apply labels then you must give the PAT `triage` level rights on `issues`. The `triage` level allows the PAT to apply/dismiss existing labels. +## Running using a fine-grained token + +### Permissions + +A fine-grained token must have these permissions: + +| Permission | Access | Level | +| ------------------- | ---------------- | ------------------------------ | +| `Members` | `Read-only` | _Organization_ | +| `Commit statuses` | `Read and write` | _Repository_ or _Organization_ | +| `Contents` | `Read and write` | _Repository_ or _Organization_ | +| `Dependabot alerts` | `Read-only` | _Repository_ or _Organization_ | +| `Issues` | `Read and write` | _Repository_ or _Organization_ | +| `Pull requests` | `Read and write` | _Repository_ or _Organization_ | +| `Workflows` | `Read and write` | _Repository_ or _Organization_ | + + +!!! tip "Use a bot role account" + Consider creating a GitHub App to use instead of using your own GitHub user account. + ## Running as a GitHub App Instead of a bot account with a Personal Access Token you can run `renovate` as a self-hosted [GitHub App](https://docs.github.com/en/developers/apps/getting-started-with-apps). diff --git a/lib/modules/platform/github/types.ts b/lib/modules/platform/github/types.ts index cd6feee577b747..c494bb8e8ff3f8 100644 --- a/lib/modules/platform/github/types.ts +++ b/lib/modules/platform/github/types.ts @@ -95,8 +95,8 @@ export interface LocalRepoConfig { parentRepo: string | null; forkOrg?: string; forkToken?: string; + forkCreation?: boolean; prList: GhPr[] | null; - issueList: any[] | null; mergeMethod: 'rebase' | 'squash' | 'merge'; defaultBranch: string; repositoryOwner: string; @@ -115,6 +115,9 @@ export type BranchProtection = any; export interface GhRepo { id: string; isFork: boolean; + parent?: { + nameWithOwner: string; + }; isArchived: boolean; nameWithOwner: string; autoMergeAllowed: boolean; @@ -129,6 +132,7 @@ export interface GhRepo { oid: string; }; }; + issues: { nodes: unknown[] }; } export interface GhAutomergeResponse { diff --git a/lib/modules/platform/gitlab/index.spec.ts b/lib/modules/platform/gitlab/index.spec.ts index ee59b73ca19363..8366103aaef75a 100644 --- a/lib/modules/platform/gitlab/index.spec.ts +++ b/lib/modules/platform/gitlab/index.spec.ts @@ -2320,6 +2320,84 @@ describe('modules/platform/gitlab/index', () => { }); }); + it('does not try to remove "report_approver" and "code_owner" approval rules', async () => { + await initPlatform('13.3.6-ee'); + httpMock + .scope(gitlabApiHost) + .post('/api/v4/projects/undefined/merge_requests') + .reply(200, { + id: 1, + iid: 12345, + title: 'some title', + }) + .get('/api/v4/projects/undefined/merge_requests/12345') + .reply(200) + .get('/api/v4/projects/undefined/merge_requests/12345') + .reply(200, { + merge_status: 'can_be_merged', + pipeline: { + id: 29626725, + sha: '2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f', + ref: 'patch-28', + status: 'success', + }, + }) + .put('/api/v4/projects/undefined/merge_requests/12345/merge') + .reply(200) + .get('/api/v4/projects/undefined/merge_requests/12345/approval_rules') + .reply(200, [ + { + name: 'RegularApproverRule', + rule_type: 'regular', + id: 50006, + }, + { + name: 'AnotherRegularApproverRule', + rule_type: 'regular', + id: 50007, + }, + { + name: 'Coverage-Check', + rule_type: 'report_approver', + id: 50008, + }, + { + name: 'path/to/folder', + rule_type: 'code_owner', + id: 50009, + }, + ]) + .delete( + '/api/v4/projects/undefined/merge_requests/12345/approval_rules/50006', + ) + .reply(200) + .delete( + '/api/v4/projects/undefined/merge_requests/12345/approval_rules/50007', + ) + .reply(200) + .post('/api/v4/projects/undefined/merge_requests/12345/approval_rules') + .reply(200); + expect( + await gitlab.createPr({ + sourceBranch: 'some-branch', + targetBranch: 'master', + prTitle: 'some-title', + prBody: 'the-body', + labels: [], + platformOptions: { + usePlatformAutomerge: true, + gitLabIgnoreApprovals: true, + }, + }), + ).toStrictEqual({ + id: 1, + iid: 12345, + number: 12345, + sourceBranch: 'some-branch', + title: 'some title', + }); + }); + it('does not try to create already existing approval rule', async () => { await initPlatform('13.3.6-ee'); httpMock @@ -2796,6 +2874,67 @@ describe('modules/platform/gitlab/index', () => { }), ).toResolve(); }); + + it('adds and removes labels', async () => { + await initPlatform('13.3.6-ee'); + httpMock + .scope(gitlabApiHost) + .get( + '/api/v4/projects/undefined/merge_requests?per_page=100&scope=created_by_me', + ) + .reply(200, [ + { + iid: 1, + source_branch: 'branch-a', + title: 'branch a pr', + state: 'open', + }, + ]) + .put('/api/v4/projects/undefined/merge_requests/1') + .reply(200); + await expect( + gitlab.updatePr({ + number: 1, + prTitle: 'title', + prBody: 'body', + state: 'closed', + addLabels: ['new_label'], + removeLabels: ['old_label'], + }), + ).toResolve(); + }); + }); + + describe('reattemptPlatformAutomerge(number, platformOptions)', () => { + const pr = { + number: 12345, + platformOptions: { + usePlatformAutomerge: true, + }, + }; + + it('should set automatic merge', async () => { + await initPlatform('13.3.6-ee'); + httpMock + .scope(gitlabApiHost) + .get('/api/v4/projects/undefined/merge_requests/12345') + .reply(200) + .get('/api/v4/projects/undefined/merge_requests/12345') + .reply(200, { + merge_status: 'can_be_merged', + pipeline: { + status: 'running', + }, + }) + .put('/api/v4/projects/undefined/merge_requests/12345/merge') + .reply(200); + + await expect(gitlab.reattemptPlatformAutomerge?.(pr)).toResolve(); + + expect(logger.debug).toHaveBeenLastCalledWith( + 'PR platform automerge re-attempted...prNo: 12345', + ); + }); }); describe('mergePr(pr)', () => { diff --git a/lib/modules/platform/gitlab/index.ts b/lib/modules/platform/gitlab/index.ts index 50d61078ee5b5e..0988ad0bb81750 100644 --- a/lib/modules/platform/gitlab/index.ts +++ b/lib/modules/platform/gitlab/index.ts @@ -48,6 +48,7 @@ import type { PlatformPrOptions, PlatformResult, Pr, + ReattemptPlatformAutomergeConfig, RepoParams, RepoResult, UpdatePrConfig, @@ -266,7 +267,7 @@ function getRepoUrl( res.body.http_url_to_repo === null ) { if (res.body.http_url_to_repo === null) { - logger.debug('no http_url_to_repo found. Falling back to old behaviour.'); + logger.debug('no http_url_to_repo found. Falling back to old behavior.'); } if (process.env.GITLAB_IGNORE_REPO_URL) { logger.warn( @@ -608,7 +609,10 @@ async function ignoreApprovals(pr: number): Promise { ); const existingRegularApproverRules = rules?.filter( ({ rule_type, name }) => - rule_type !== 'any_approver' && name !== ruleName, + rule_type !== 'any_approver' && + name !== ruleName && + rule_type !== 'report_approver' && + rule_type !== 'code_owner', ); if (existingRegularApproverRules?.length) { @@ -814,6 +818,8 @@ export async function updatePr({ number: iid, prTitle, prBody: description, + addLabels, + removeLabels, state, platformOptions, targetBranch, @@ -837,6 +843,14 @@ export async function updatePr({ body.target_branch = targetBranch; } + if (addLabels) { + body.add_labels = addLabels; + } + + if (removeLabels) { + body.remove_labels = removeLabels; + } + await gitlabApi.putJson( `projects/${config.repository}/merge_requests/${iid}`, { body }, @@ -845,8 +859,15 @@ export async function updatePr({ if (platformOptions?.autoApprove) { await approvePr(iid); } +} +export async function reattemptPlatformAutomerge({ + number: iid, + platformOptions, +}: ReattemptPlatformAutomergeConfig): Promise { await tryPrAutomerge(iid, platformOptions); + + logger.debug(`PR platform automerge re-attempted...prNo: ${iid}`); } export async function mergePr({ id }: MergePRConfig): Promise { @@ -1048,11 +1069,14 @@ export async function setBranchStatus({ export async function getIssueList(): Promise { if (!config.issueList) { - const query = getQueryString({ + const searchParams: Record = { per_page: '100', - scope: 'created_by_me', state: 'opened', - }); + }; + if (!config.ignorePrAuthor) { + searchParams.scope = 'created_by_me'; + } + const query = getQueryString(searchParams); const res = await gitlabApi.getJson< { iid: number; title: string; labels: string[] }[] >(`projects/${config.repository}/issues?${query}`, { diff --git a/lib/modules/platform/gitlab/readme.md b/lib/modules/platform/gitlab/readme.md index 72e49d78bf02b6..395972f3845fd1 100644 --- a/lib/modules/platform/gitlab/readme.md +++ b/lib/modules/platform/gitlab/readme.md @@ -36,6 +36,9 @@ If you're using a private [GitLab container registry](https://docs.gitlab.com/ee - Make sure the user that owns the `RENOVATE_TOKEN` PAT is a member of the corresponding GitLab projects/groups with the right permissions. - Make sure the `RENOVATE_TOKEN` PAT has the `read_registry` scope. +You may want to set `FORCE_COLOR: 3` or `TERM: ansi` to the job, in order to get colored output. +[GitLab Runner runs the container’s shell in non-interactive mode, so the shell’s `TERM` environment variable is set to `dumb`.](https://docs.gitlab.com/ee/ci/yaml/script.html#job-log-output-is-not-formatted-as-expected-or-contains-unexpected-characters) + ## Features awaiting implementation - The `automergeStrategy` configuration option has not been implemented for this platform, and all values behave as if the value `auto` was used. Renovate will accept the Merge Request without further configuration, and respect the strategy defined in the Merge Request, and this cannot be overridden yet diff --git a/lib/modules/platform/types.ts b/lib/modules/platform/types.ts index 8a27564666d997..28f622cd61a33c 100644 --- a/lib/modules/platform/types.ts +++ b/lib/modules/platform/types.ts @@ -41,6 +41,7 @@ export interface RepoParams { repository: string; endpoint?: string; gitUrl?: GitUrlOption; + forkCreation?: boolean; forkOrg?: string; forkToken?: string; forkProcessing?: 'enabled' | 'disabled'; @@ -55,6 +56,7 @@ export interface PrDebugData { createdInVer: string; updatedInVer: string; targetBranch: string; + labels?: string[]; } export interface PrBodyStruct { @@ -121,6 +123,32 @@ export interface UpdatePrConfig { prBody?: string; state?: 'open' | 'closed'; targetBranch?: string; + + /** + * This field allows for label management and is designed to + * accommodate the different label update methods on various platforms. + * + * - For Gitea, labels are updated by replacing the entire labels array. + * - In the case of GitHub and GitLab, specific endpoints exist + * for adding and removing labels. + */ + labels?: string[] | null; + + /** + * Specifies an array of labels to be added. + * @see {@link labels} + */ + addLabels?: string[] | null; + + /** + * Specifies an array of labels to be removed. + * @see {@link labels} + */ + removeLabels?: string[] | null; +} +export interface ReattemptPlatformAutomergeConfig { + number: number; + platformOptions?: PlatformPrOptions; } export interface EnsureIssueConfig { title: string; @@ -173,10 +201,22 @@ export type EnsureCommentRemovalConfig = export type EnsureIssueResult = 'updated' | 'created'; +export type RepoSortMethod = + | 'alpha' + | 'created' + | 'updated' + | 'size' + | 'id' + | null; + +export type SortMethod = 'asc' | 'desc' | null; export interface AutodiscoverConfig { topics?: string[]; + sort?: RepoSortMethod; + order?: SortMethod; includeMirrors?: boolean; namespaces?: string[]; + projects?: string[]; } export interface Platform { @@ -209,6 +249,7 @@ export interface Platform { getRepos(config?: AutodiscoverConfig): Promise; getBranchForceRebase?(branchName: string): Promise; deleteLabel(number: number, label: string): Promise; + addLabel?(number: number, label: string): Promise; setBranchStatus(branchStatusConfig: BranchStatusConfig): Promise; getBranchStatusCheck( branchName: string, @@ -224,6 +265,9 @@ export interface Platform { getPr(number: number): Promise; findPr(findPRConfig: FindPRConfig): Promise; refreshPr?(number: number): Promise; + reattemptPlatformAutomerge?( + prConfig: ReattemptPlatformAutomergeConfig, + ): Promise; getBranchStatus( branchName: string, internalChecksAsSuccess: boolean, diff --git a/lib/modules/versioning/api.ts b/lib/modules/versioning/api.ts index aaf75903ff3150..a2f52a8a738243 100644 --- a/lib/modules/versioning/api.ts +++ b/lib/modules/versioning/api.ts @@ -36,6 +36,7 @@ import * as semverCoerced from './semver-coerced'; import * as swift from './swift'; import type { VersioningApi, VersioningApiConstructor } from './types'; import * as ubuntu from './ubuntu'; +import * as unity3d from './unity3d'; const api = new Map(); export default api; @@ -77,3 +78,4 @@ api.set(semver.id, semver.api); api.set(semverCoerced.id, semverCoerced.api); api.set(swift.id, swift.api); api.set(ubuntu.id, ubuntu.api); +api.set(unity3d.id, unity3d.api); diff --git a/lib/modules/versioning/cargo/index.spec.ts b/lib/modules/versioning/cargo/index.spec.ts index 6d006a97abd578..8198b12f405592 100644 --- a/lib/modules/versioning/cargo/index.spec.ts +++ b/lib/modules/versioning/cargo/index.spec.ts @@ -125,13 +125,13 @@ describe('modules/versioning/cargo/index', () => { ${'1.0.0'} | ${'replace'} | ${'1.0.0'} | ${'2.0.7'} | ${'2.0.0'} ${'^1'} | ${'bump'} | ${'1.0.0'} | ${'2.1.7'} | ${'^2.1.7'} ${'~1'} | ${'bump'} | ${'1.0.0'} | ${'1.1.7'} | ${'~1.1.7'} - ${'5'} | ${'bump'} | ${'5.0.0'} | ${'5.1.7'} | ${'5'} - ${'5'} | ${'bump'} | ${'5.0.0'} | ${'6.1.7'} | ${'6'} - ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'5.0.7'} | ${'5.0'} - ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'5.1.7'} | ${'5.1'} - ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'6.1.7'} | ${'6.1'} - ${'0.5'} | ${'bump'} | ${'0.5.0'} | ${'0.5.1'} | ${'0.5'} - ${'0.5'} | ${'bump'} | ${'0.5.0'} | ${'0.6.1'} | ${'0.6'} + ${'5'} | ${'bump'} | ${'5.0.0'} | ${'5.1.7'} | ${'5.1.7'} + ${'5'} | ${'bump'} | ${'5.0.0'} | ${'6.1.7'} | ${'6.1.7'} + ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'5.0.7'} | ${'5.0.7'} + ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'5.1.7'} | ${'5.1.7'} + ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'6.1.7'} | ${'6.1.7'} + ${'0.5'} | ${'bump'} | ${'0.5.0'} | ${'0.5.1'} | ${'0.5.1'} + ${'0.5'} | ${'bump'} | ${'0.5.0'} | ${'0.6.1'} | ${'0.6.1'} ${'5.0'} | ${'replace'} | ${'5.0.0'} | ${'5.1.7'} | ${'5.0'} ${'5.0'} | ${'replace'} | ${'5.0.0'} | ${'6.1.7'} | ${'6.0'} ${'0.5'} | ${'replace'} | ${'0.5.0'} | ${'0.6.1'} | ${'0.6'} diff --git a/lib/modules/versioning/cargo/index.ts b/lib/modules/versioning/cargo/index.ts index 36cdd3d5bde523..c78fc095cecfcb 100644 --- a/lib/modules/versioning/cargo/index.ts +++ b/lib/modules/versioning/cargo/index.ts @@ -94,6 +94,10 @@ function getNewValue({ if (!currentValue || currentValue === '*') { return rangeStrategy === 'pin' ? `=${newVersion}` : currentValue; } + // If the current value is a simple version, bump to fully specified newVersion + if (rangeStrategy === 'bump' && regEx(/^\d+(?:\.\d+)*$/).test(currentValue)) { + return newVersion; + } if (rangeStrategy === 'pin' || isSingleVersion(currentValue)) { let res = '='; if (currentValue.startsWith('= ')) { diff --git a/lib/modules/versioning/conan/range.ts b/lib/modules/versioning/conan/range.ts index 8984753168cccb..885e2c269911c8 100644 --- a/lib/modules/versioning/conan/range.ts +++ b/lib/modules/versioning/conan/range.ts @@ -44,7 +44,9 @@ export function getPatch(version: string): null | number { if (typeof cleanerVersion === 'string') { const newVersion = semver.valid( - semver.coerce(cleanedVersion, options), + semver.coerce(cleanedVersion, { + loose: false, + }), options, ); return Number(newVersion?.split('.')[2]); diff --git a/lib/modules/versioning/go-mod-directive/index.spec.ts b/lib/modules/versioning/go-mod-directive/index.spec.ts index e133594d30b22a..13b6d0c29753b9 100644 --- a/lib/modules/versioning/go-mod-directive/index.spec.ts +++ b/lib/modules/versioning/go-mod-directive/index.spec.ts @@ -2,12 +2,13 @@ import { api as semver } from '.'; describe('modules/versioning/go-mod-directive/index', () => { it.each` - version | range | expected - ${'1.16.0'} | ${'1.16'} | ${true} - ${'1.16.1'} | ${'1.16'} | ${true} - ${'1.15.0'} | ${'1.16'} | ${false} - ${'1.19.1'} | ${'1.16'} | ${true} - ${'2.0.0'} | ${'1.16'} | ${false} + version | range | expected + ${'1.16.0'} | ${'1.16'} | ${true} + ${'1.16.1'} | ${'1.16'} | ${true} + ${'1.15.0'} | ${'1.16'} | ${false} + ${'1.19.1'} | ${'1.16'} | ${true} + ${'2.0.0'} | ${'1.16'} | ${false} + ${'1.22.2'} | ${'1.21.9'} | ${true} `( 'matches("$version", "$range") === "$expected"', ({ version, range, expected }) => { @@ -70,10 +71,12 @@ describe('modules/versioning/go-mod-directive/index', () => { ${'1.16'} | ${'bump'} | ${'1.16.4'} | ${'1.17.0'} | ${'1.17'} ${'1.16'} | ${'bump'} | ${'1.16.4'} | ${'1.16.4'} | ${'1.16'} ${'1.16'} | ${'replace'} | ${'1.16.4'} | ${'1.16.4'} | ${'1.16'} - ${'1.16'} | ${'replace'} | ${'1.21.2'} | ${'1.21.2'} | ${'1.21.2'} + ${'1.16'} | ${'replace'} | ${'1.21.2'} | ${'1.21.2'} | ${'1.16'} ${'1.16'} | ${'widen'} | ${'1.16.4'} | ${'1.16.4'} | ${'1.16'} ${'1.16'} | ${'bump'} | ${'1.16.4'} | ${'1.21.3'} | ${'1.21.3'} ${'1.21.2'} | ${'bump'} | ${'1.21.2'} | ${'1.21.3'} | ${'1.21.3'} + ${'1.21.2'} | ${'replace'} | ${'1.21.2'} | ${'1.22.2'} | ${'1.21.2'} + ${'1.21.2'} | ${'replace'} | ${'1.21.2'} | ${'2.0.0'} | ${'2.0.0'} `( 'getNewValue("$currentValue", "$rangeStrategy", "$currentVersion", "$newVersion") === "$expected"', ({ currentValue, rangeStrategy, currentVersion, newVersion, expected }) => { diff --git a/lib/modules/versioning/go-mod-directive/index.ts b/lib/modules/versioning/go-mod-directive/index.ts index 820c4b62ddb8cd..b09f8a2775521e 100644 --- a/lib/modules/versioning/go-mod-directive/index.ts +++ b/lib/modules/versioning/go-mod-directive/index.ts @@ -30,11 +30,8 @@ function getNewValue({ } return shorten(newVersion); } - if (rangeStrategy === 'replace' && !matches(currentValue, newVersion)) { - if (npm.matches(newVersion, '>=1.20.0')) { - return newVersion; - } - return shorten(newVersion); + if (rangeStrategy === 'replace' && !matches(newVersion, currentValue)) { + return newVersion; } return currentValue; } diff --git a/lib/modules/versioning/helm/index.spec.ts b/lib/modules/versioning/helm/index.spec.ts index 748bac896033f0..1bda42481fcf56 100644 --- a/lib/modules/versioning/helm/index.spec.ts +++ b/lib/modules/versioning/helm/index.spec.ts @@ -41,11 +41,11 @@ describe('modules/versioning/helm/index', () => { ${'~1.0'} | ${'bump'} | ${'1.0.0'} | ${'1.0.7-prerelease.1'} | ${'~1.0.7-prerelease.1'} ${'^1'} | ${'bump'} | ${'1.0.0'} | ${'2.1.7'} | ${'^2.1.7'} ${'~1'} | ${'bump'} | ${'1.0.0'} | ${'1.1.7'} | ${'~1.1.7'} - ${'5'} | ${'bump'} | ${'5.0.0'} | ${'5.1.7'} | ${'5'} - ${'5'} | ${'bump'} | ${'5.0.0'} | ${'6.1.7'} | ${'6'} - ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'5.0.7'} | ${'5.0'} - ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'5.1.7'} | ${'5.1'} - ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'6.1.7'} | ${'6.1'} + ${'5'} | ${'bump'} | ${'5.0.0'} | ${'5.1.7'} | ${'5.1.7'} + ${'5'} | ${'bump'} | ${'5.0.0'} | ${'6.1.7'} | ${'6.1.7'} + ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'5.0.7'} | ${'5.0.7'} + ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'5.1.7'} | ${'5.1.7'} + ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'6.1.7'} | ${'6.1.7'} ${'>=1.0.0'} | ${'bump'} | ${'1.0.0'} | ${'1.1.0'} | ${'>=1.1.0'} ${'>= 1.0.0'} | ${'bump'} | ${'1.0.0'} | ${'1.1.0'} | ${'>= 1.1.0'} ${'=1.0.0'} | ${'replace'} | ${'1.0.0'} | ${'1.1.0'} | ${'=1.1.0'} diff --git a/lib/modules/versioning/npm/index.spec.ts b/lib/modules/versioning/npm/index.spec.ts index c8f22b65a7e5e4..ee55880a6020e5 100644 --- a/lib/modules/versioning/npm/index.spec.ts +++ b/lib/modules/versioning/npm/index.spec.ts @@ -86,11 +86,11 @@ describe('modules/versioning/npm/index', () => { ${'~1.0'} | ${'bump'} | ${'1.0.0'} | ${'1.0.7-prerelease.1'} | ${'~1.0.7-prerelease.1'} ${'^1'} | ${'bump'} | ${'1.0.0'} | ${'2.1.7'} | ${'^2.1.7'} ${'~1'} | ${'bump'} | ${'1.0.0'} | ${'1.1.7'} | ${'~1.1.7'} - ${'5'} | ${'bump'} | ${'5.0.0'} | ${'5.1.7'} | ${'5'} - ${'5'} | ${'bump'} | ${'5.0.0'} | ${'6.1.7'} | ${'6'} - ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'5.0.7'} | ${'5.0'} - ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'5.1.7'} | ${'5.1'} - ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'6.1.7'} | ${'6.1'} + ${'5'} | ${'bump'} | ${'5.0.0'} | ${'5.1.7'} | ${'5.1.7'} + ${'5'} | ${'bump'} | ${'5.0.0'} | ${'6.1.7'} | ${'6.1.7'} + ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'5.0.7'} | ${'5.0.7'} + ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'5.1.7'} | ${'5.1.7'} + ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'6.1.7'} | ${'6.1.7'} ${'>=1.0.0'} | ${'bump'} | ${'1.0.0'} | ${'1.1.0'} | ${'>=1.1.0'} ${'>= 1.0.0'} | ${'bump'} | ${'1.0.0'} | ${'1.1.0'} | ${'>= 1.1.0'} ${'=1.0.0'} | ${'replace'} | ${'1.0.0'} | ${'1.1.0'} | ${'=1.1.0'} diff --git a/lib/modules/versioning/npm/range.ts b/lib/modules/versioning/npm/range.ts index 9481eaad2d7ba2..24b1dec31b6c7e 100644 --- a/lib/modules/versioning/npm/range.ts +++ b/lib/modules/versioning/npm/range.ts @@ -132,12 +132,7 @@ export function getNewValue({ if (rangeStrategy === 'bump') { if (parsedRange.length === 1) { if (!element.operator) { - return getNewValue({ - currentValue, - rangeStrategy: 'replace', - currentVersion, - newVersion, - }); + return stripV(newVersion); } if (element.operator === '^') { return `^${stripV(newVersion)}`; diff --git a/lib/modules/versioning/pep440/index.spec.ts b/lib/modules/versioning/pep440/index.spec.ts index bb2ad28d78b037..c9c024ac259f44 100644 --- a/lib/modules/versioning/pep440/index.spec.ts +++ b/lib/modules/versioning/pep440/index.spec.ts @@ -8,6 +8,7 @@ describe('modules/versioning/pep440/index', () => { ${'1.9'} | ${true} ${'17.04.0'} | ${true} ${'==1.2.3'} | ${true} + ${'==1.2.3.0'} | ${true} ${'==1.2.3rc0'} | ${true} ${'~=1.2.3'} | ${true} ${'==1.2.*'} | ${true} @@ -37,6 +38,15 @@ describe('modules/versioning/pep440/index', () => { expect(pep440.equals(a, b)).toBe(expected); }); + it.each` + a | b | expected + ${'1.0'} | ${'>=1.0.0'} | ${true} + ${'1.6.2'} | ${'<2.2.1.0'} | ${true} + ${'>=3.8'} | ${'>=3.9'} | ${false} + `('matches($a, $b) === $expected', ({ a, b, expected }) => { + expect(pep440.matches(a, b)).toBe(expected); + }); + it.each` version | isSingle ${'1.2.3'} | ${true} diff --git a/lib/modules/versioning/pep440/index.ts b/lib/modules/versioning/pep440/index.ts index 6de5a74ad4931b..f37b731ab0278e 100644 --- a/lib/modules/versioning/pep440/index.ts +++ b/lib/modules/versioning/pep440/index.ts @@ -16,7 +16,7 @@ export const supportedRangeStrategies: RangeStrategy[] = [ const { compare: sortVersions, - satisfies: matches, + satisfies, valid, validRange, explain, @@ -74,6 +74,10 @@ export { isVersion, matches }; const equals = (version1: string, version2: string): boolean => isVersion(version1) && isVersion(version2) && eq(version1, version2); +function matches(version: string, range: string): boolean { + return isVersion(version) && isValid(range) && satisfies(version, range); +} + export const api: VersioningApi = { equals, getMajor, diff --git a/lib/modules/versioning/pep440/range.spec.ts b/lib/modules/versioning/pep440/range.spec.ts index 193d558fd6749b..5c913f39dc8971 100644 --- a/lib/modules/versioning/pep440/range.spec.ts +++ b/lib/modules/versioning/pep440/range.spec.ts @@ -1,17 +1,34 @@ -import { checkRangeAndRemoveUnnecessaryRangeLimit } from './range'; +import { logger } from '../../../logger'; +import { checkRangeAndRemoveUnnecessaryRangeLimit, getNewValue } from './range'; -it.each` - rangeInput | newVersion | expected - ${'==4.1.*,>=3.2.2'} | ${'4.1.1'} | ${'==4.1.*'} - ${'==4.0.*,>=3.2.2'} | ${'4.0.0'} | ${'==4.0.*'} - ${'==7.2.*'} | ${'7.2.0'} | ${'==7.2.*'} -`( - 'checkRange("$rangeInput, "$newVersion"") === "$expected"', - ({ rangeInput, newVersion, expected }) => { - const res = checkRangeAndRemoveUnnecessaryRangeLimit( - rangeInput, - newVersion, +describe('modules/versioning/pep440/range', () => { + it.each` + rangeInput | newVersion | expected + ${'==4.1.*,>=3.2.2'} | ${'4.1.1'} | ${'==4.1.*'} + ${'==4.0.*,>=3.2.2'} | ${'4.0.0'} | ${'==4.0.*'} + ${'==7.2.*'} | ${'7.2.0'} | ${'==7.2.*'} + `( + 'checkRange("$rangeInput, "$newVersion"") === "$expected"', + ({ rangeInput, newVersion, expected }) => { + const res = checkRangeAndRemoveUnnecessaryRangeLimit( + rangeInput, + newVersion, + ); + expect(res).toEqual(expected); + }, + ); + + it('returns null without warning if new version is excluded from range', () => { + const res = getNewValue({ + currentValue: '>=1.25.0,<2,!=1.32.0', + rangeStrategy: 'auto', + newVersion: '1.32.0', + currentVersion: '1.25.0', + }); + expect(res).toBeNull(); + expect(logger.debug).toHaveBeenCalledWith( + 'Cannot calculate new value as the newVersion:`1.32.0` is excluded from range: `>=1.25.0,<2,!=1.32.0`', ); - expect(res).toEqual(expected); - }, -); + expect(logger.warn).not.toHaveBeenCalled(); + }); +}); diff --git a/lib/modules/versioning/pep440/range.ts b/lib/modules/versioning/pep440/range.ts index 0c94a3c4c894a8..ea36bb335bfc18 100644 --- a/lib/modules/versioning/pep440/range.ts +++ b/lib/modules/versioning/pep440/range.ts @@ -129,6 +129,18 @@ export function getNewValue({ return null; } + // return early if newVersion is excluded from range + if ( + ranges.some( + (range) => range.operator === '!=' && range.version === newVersion, + ) + ) { + logger.debug( + `Cannot calculate new value as the newVersion:\`${newVersion}\` is excluded from range: \`${currentValue}\``, + ); + return null; + } + switch (rangeStrategy) { case 'auto': case 'replace': @@ -191,6 +203,7 @@ export function getNewValue({ newVersion, ); + // istanbul ignore if if (!satisfies(newVersion, checkedResult)) { // we failed at creating the range logger.warn( diff --git a/lib/modules/versioning/poetry/index.spec.ts b/lib/modules/versioning/poetry/index.spec.ts index 549bcd7dd805ca..54279366f8549b 100644 --- a/lib/modules/versioning/poetry/index.spec.ts +++ b/lib/modules/versioning/poetry/index.spec.ts @@ -3,19 +3,23 @@ import { api as versioning } from '.'; describe('modules/versioning/poetry/index', () => { describe('equals', () => { it.each` - a | b | expected - ${'1'} | ${'1'} | ${true} - ${'1.0'} | ${'1'} | ${true} - ${'1.0.0'} | ${'1'} | ${true} - ${'1.9.0'} | ${'1.9'} | ${true} - ${'1'} | ${'2'} | ${false} - ${'1.9.1'} | ${'1.9'} | ${false} - ${'1.9-beta'} | ${'1.9'} | ${false} - ${'1.9b0'} | ${'1.9'} | ${false} - ${'1.9b0'} | ${'1.9.0-beta.0'} | ${true} - ${'1.9-0'} | ${'1.9.0-post.0'} | ${true} - ${'1.9.0-post'} | ${'1.9.0-post.0'} | ${true} - ${'1.9.0dev0'} | ${'1.9.0-dev.0'} | ${true} + a | b | expected + ${'1'} | ${'1'} | ${true} + ${'1.0'} | ${'1'} | ${true} + ${'1.0.0'} | ${'1'} | ${true} + ${'1.9.0'} | ${'1.9'} | ${true} + ${'1'} | ${'2'} | ${false} + ${'1.9.1'} | ${'1.9'} | ${false} + ${'1.9-beta'} | ${'1.9'} | ${false} + ${'1.9b0'} | ${'1.9'} | ${false} + ${'1.9b0'} | ${'1.9.0-beta.0'} | ${true} + ${'1.9.01b01'} | ${'1.9.1-beta.1'} | ${true} + ${'1.9-0'} | ${'1.9.0-post.0'} | ${true} + ${'1.9.0-post'} | ${'1.9.0-post.0'} | ${true} + ${'1.9.01-post'} | ${'1.9.1-post.0'} | ${true} + ${'1.9.0dev0'} | ${'1.9.0-dev.0'} | ${true} + ${'1.9.01pre'} | ${'1.9.1-pre'} | ${true} + ${'1.9.pre'} | ${'1.9.pre'} | ${true} `('equals("$a", "$b") === $expected', ({ a, b, expected }) => { expect(versioning.equals(a, b)).toBe(expected); }); @@ -201,11 +205,11 @@ describe('modules/versioning/poetry/index', () => { ${'^0.5.15'} | ${'replace'} | ${'0.5.15'} | ${'0.6b.4'} | ${'^0.5.15'} ${'^1'} | ${'bump'} | ${'1.0.0'} | ${'2.1.7'} | ${'^2.1.7'} ${'~1'} | ${'bump'} | ${'1.0.0'} | ${'1.1.7'} | ${'~1.1.7'} - ${'5'} | ${'bump'} | ${'5.0.0'} | ${'5.1.7'} | ${'5'} - ${'5'} | ${'bump'} | ${'5.0.0'} | ${'6.1.7'} | ${'6'} - ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'5.0.7'} | ${'5.0'} - ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'5.1.7'} | ${'5.1'} - ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'6.1.7'} | ${'6.1'} + ${'5'} | ${'bump'} | ${'5.0.0'} | ${'5.1.7'} | ${'5.1.7'} + ${'5'} | ${'bump'} | ${'5.0.0'} | ${'6.1.7'} | ${'6.1.7'} + ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'5.0.7'} | ${'5.0.7'} + ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'5.1.7'} | ${'5.1.7'} + ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'6.1.7'} | ${'6.1.7'} ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'6.b0.0'} | ${'5.0'} ${'5.0'} | ${'replace'} | ${'5.0.0'} | ${'6.1.7'} | ${'6.1'} ${'=1.0.0'} | ${'replace'} | ${'1.0.0'} | ${'1.1.0'} | ${'=1.1.0'} diff --git a/lib/modules/versioning/poetry/transform.ts b/lib/modules/versioning/poetry/transform.ts index d5490768493870..528ed07bc81158 100644 --- a/lib/modules/versioning/poetry/transform.ts +++ b/lib/modules/versioning/poetry/transform.ts @@ -63,12 +63,18 @@ export function poetry2semver( const parts = [releaseParts.map((num) => num.toString()).join('.')]; if (pre !== null) { + // trim leading zeros from valid numbers + pre.number = pre.number.replace(regEx(/^0+(\d+)/), '$1'); parts.push(`-${pre.letter}.${pre.number}`); } if (post !== null) { + // trim leading zeros from valid numbers + post.number = post.number.replace(regEx(/^0+(\d+)/), '$1'); parts.push(`-${post.letter}.${post.number}`); } if (dev !== null) { + // trim leading zeros from valid numbers + dev.number = dev.number.replace(regEx(/^0+(\d+)/), '$1'); parts.push(`-${dev.letter}.${dev.number}`); } diff --git a/lib/modules/versioning/python/index.spec.ts b/lib/modules/versioning/python/index.spec.ts index 15043550a57a28..90abbf2d373535 100644 --- a/lib/modules/versioning/python/index.spec.ts +++ b/lib/modules/versioning/python/index.spec.ts @@ -1,5 +1,3 @@ -import { partial } from '../../../../test/util'; -import type { NewValueConfig } from '../types'; import { api as versioning } from '.'; describe('modules/versioning/python/index', () => { @@ -96,9 +94,68 @@ describe('modules/versioning/python/index', () => { }, ); - test('getNewValue()', () => { - expect(versioning.getNewValue(partial())).toBeNull(); - }); + it.each` + currentValue | rangeStrategy | currentVersion | newVersion | expected + ${'1.0.0'} | ${'bump'} | ${'1.0.0'} | ${'1.1.0'} | ${'1.1.0'} + ${' 1.0.0'} | ${'bump'} | ${'1.0.0'} | ${'1.1.0'} | ${'1.1.0'} + ${'1.0.0'} | ${'bump'} | ${'1.0.0'} | ${'1.1.0'} | ${'1.1.0'} + ${'=1.0.0'} | ${'bump'} | ${'1.0.0'} | ${'1.1.0'} | ${'=1.1.0'} + ${'= 1.0.0'} | ${'bump'} | ${'1.0.0'} | ${'1.1.0'} | ${'=1.1.0'} + ${'= 1.0.0'} | ${'bump'} | ${'1.0.0'} | ${'1.1.0'} | ${'=1.1.0'} + ${' = 1.0.0'} | ${'bump'} | ${'1.0.0'} | ${'1.1.0'} | ${'=1.1.0'} + ${' = 1.0.0'} | ${'bump'} | ${'1.0.0'} | ${'1.1.0'} | ${'=1.1.0'} + ${'= 1.0.0'} | ${'bump'} | ${'1.0.0'} | ${'1.1.0'} | ${'=1.1.0'} + ${'^1.0'} | ${'bump'} | ${'1.0.0'} | ${'1.0.7'} | ${'^1.0.7'} + ${'^1.0.0'} | ${'replace'} | ${'1.0.0'} | ${'2.0.7'} | ${'^2.0.0'} + ${'^5.0.3'} | ${'replace'} | ${'5.3.1'} | ${'5.5'} | ${'^5.0.3'} + ${'1.0.0'} | ${'replace'} | ${'1.0.0'} | ${'2.0.7'} | ${'2.0.7'} + ${'^1.0.0'} | ${'replace'} | ${'1.0.0'} | ${'2.0.7'} | ${'^2.0.0'} + ${'^0.5.15'} | ${'replace'} | ${'0.5.15'} | ${'0.6'} | ${'^0.5.15'} + ${'^0.5.15'} | ${'replace'} | ${'0.5.15'} | ${'0.6b.4'} | ${'^0.5.15'} + ${'^1'} | ${'bump'} | ${'1.0.0'} | ${'2.1.7'} | ${'^2.1.7'} + ${'~1'} | ${'bump'} | ${'1.0.0'} | ${'1.1.7'} | ${'~1.1.7'} + ${'5'} | ${'bump'} | ${'5.0.0'} | ${'5.1.7'} | ${'5.1.7'} + ${'5'} | ${'bump'} | ${'5.0.0'} | ${'6.1.7'} | ${'6.1.7'} + ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'5.0.7'} | ${'5.0.7'} + ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'5.1.7'} | ${'5.1.7'} + ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'6.1.7'} | ${'6.1.7'} + ${'5.0'} | ${'bump'} | ${'5.0.0'} | ${'6.b0.0'} | ${'5.0'} + ${'5.0'} | ${'replace'} | ${'5.0.0'} | ${'6.1.7'} | ${'6.1'} + ${'=1.0.0'} | ${'replace'} | ${'1.0.0'} | ${'1.1.0'} | ${'=1.1.0'} + ${'^1'} | ${'bump'} | ${'1.0.0'} | ${'1.0.7rc.1'} | ${'^1.0.7-rc.1'} + ${'^1'} | ${'bump'} | ${'1.0.0'} | ${'1.0.7a0'} | ${'^1.0.7-alpha.0'} + ${'^0.8.0-alpha.0'} | ${'bump'} | ${'0.8.0-alpha.0'} | ${'0.8.0-alpha.1'} | ${'^0.8.0-alpha.1'} + ${'^0.8.0-alpha.0'} | ${'bump'} | ${'0.8.0-alpha.0'} | ${'0.8.0a1'} | ${'^0.8.0-alpha.1'} + ${'^1.0.0'} | ${'replace'} | ${'1.0.0'} | ${'1.2.3'} | ${'^1.0.0'} + ${'~1.0'} | ${'bump'} | ${'1.0.0'} | ${'1.1.7'} | ${'~1.1.7'} + ${'1.0.*'} | ${'replace'} | ${'1.0.0'} | ${'1.1.0'} | ${'1.1.*'} + ${'1.*'} | ${'replace'} | ${'1.0.0'} | ${'2.1.0'} | ${'2.*'} + ${'~0.6.1'} | ${'replace'} | ${'0.6.8'} | ${'0.7.0-rc.2'} | ${'~0.7.0-rc'} + ${'<1.3.4'} | ${'replace'} | ${'1.2.3'} | ${'1.5.0'} | ${'<1.5.1'} + ${'< 1.3.4'} | ${'replace'} | ${'1.2.3'} | ${'1.5.0'} | ${'< 1.5.1'} + ${'< 1.3.4'} | ${'replace'} | ${'1.2.3'} | ${'1.5.0'} | ${'< 1.5.1'} + ${'<=1.3.4'} | ${'replace'} | ${'1.2.3'} | ${'1.5.0'} | ${'<=1.5.0'} + ${'<= 1.3.4'} | ${'replace'} | ${'1.2.3'} | ${'1.5.0'} | ${'<= 1.5.0'} + ${'<= 1.3.4'} | ${'replace'} | ${'1.2.3'} | ${'1.5.0'} | ${'<= 1.5.0'} + ${'^1.2'} | ${'replace'} | ${'1.2.3'} | ${'2.0.0'} | ${'^2.0'} + ${'^1'} | ${'replace'} | ${'1.2.3'} | ${'2.0.0'} | ${'^2'} + ${'~1.2'} | ${'replace'} | ${'1.2.3'} | ${'2.0.0'} | ${'~2.0'} + ${'~1'} | ${'replace'} | ${'1.2.3'} | ${'2.0.0'} | ${'~2'} + ${'^2.2'} | ${'widen'} | ${'2.2.0'} | ${'3.0.0'} | ${'^2.2 || ^3.0.0'} + ${'^2.2 || ^3.0.0'} | ${'widen'} | ${'3.0.0'} | ${'4.0.0'} | ${'^2.2 || ^3.0.0 || ^4.0.0'} + ${'^3.5'} | ${'pin'} | ${'3.5'} | ${'3.5'} | ${'3.5'} + `( + 'getNewValue("$currentValue", "$rangeStrategy", "$currentVersion", "$newVersion") === "$expected"', + ({ currentValue, rangeStrategy, currentVersion, newVersion, expected }) => { + const res = versioning.getNewValue({ + currentValue, + rangeStrategy, + currentVersion, + newVersion, + }); + expect(res).toEqual(expected); + }, + ); }); it.each` diff --git a/lib/modules/versioning/python/index.ts b/lib/modules/versioning/python/index.ts index 18e31c78daca21..9fab6d9337c1b5 100644 --- a/lib/modules/versioning/python/index.ts +++ b/lib/modules/versioning/python/index.ts @@ -41,8 +41,8 @@ function minSatisfyingVersion( : pep440.minSatisfyingVersion(versions, range); } -function getNewValue(_: NewValueConfig): string | null { - return null; +function getNewValue(newValue: NewValueConfig): string | null { + return poetry.getNewValue(newValue); } function subset(subRange: string, superRange: string): boolean | undefined { diff --git a/lib/modules/versioning/rez/pattern.ts b/lib/modules/versioning/rez/pattern.ts index 8eac443ec6a5c0..96741017db5045 100644 --- a/lib/modules/versioning/rez/pattern.ts +++ b/lib/modules/versioning/rez/pattern.ts @@ -70,7 +70,7 @@ export const matchVersion = regEx( export const exactVersion = regEx( `^(?==(?${versionGroup})?)$`, ); /* Match an exact version number (e.g. ==1.0.0) */ -// inclusiveBound is called inclusive but behaviour in rez is this: +// inclusiveBound is called inclusive but behavior in rez is this: // package-1..3 will match versions 1.2.3, 2.3.4, but not 3.0.0 or above export const inclusiveBound = regEx( `^(?(?${versionGroup})?\\.\\.(?${versionGroup})?)$`, diff --git a/lib/modules/versioning/swift/index.spec.ts b/lib/modules/versioning/swift/index.spec.ts index dd733bd6b489a9..9052516ec57fba 100644 --- a/lib/modules/versioning/swift/index.spec.ts +++ b/lib/modules/versioning/swift/index.spec.ts @@ -15,6 +15,8 @@ describe('modules/versioning/swift/index', () => { version | expected ${'from: "1.2.3"'} | ${false} ${'1.2.3'} | ${true} + ${'v1.2.3'} | ${true} + ${'a'} | ${false} `('isVersion("$version") === $expected', ({ version, expected }) => { expect(!!isVersion(version)).toBe(expected); }); @@ -98,6 +100,8 @@ describe('modules/versioning/swift/index', () => { it.each` version | range | expected + ${'1.2.3'} | ${'1.2.3'} | ${true} + ${'v1.2.3'} | ${'1.2.3'} | ${true} ${'1.2.4'} | ${'..."1.2.4"'} | ${true} ${'v1.2.4'} | ${'..."1.2.4"'} | ${true} ${'1.2.4'} | ${'..."1.2.3"'} | ${false} diff --git a/lib/modules/versioning/swift/index.ts b/lib/modules/versioning/swift/index.ts index a6135eaa73221d..93510c78eaab65 100644 --- a/lib/modules/versioning/swift/index.ts +++ b/lib/modules/versioning/swift/index.ts @@ -62,6 +62,10 @@ function isLessThanRange(version: string, range: string): boolean { } function matches(version: string, range: string): boolean { + // Check if both are an exact version + if (isVersion(range) && equals(version, range)) { + return true; + } const semverRange = toSemverRange(range); return semverRange ? satisfies(version, semverRange) : false; } diff --git a/lib/modules/versioning/ubuntu/readme.md b/lib/modules/versioning/ubuntu/readme.md index fd611b3b90ebdd..49dfba8663fb19 100644 --- a/lib/modules/versioning/ubuntu/readme.md +++ b/lib/modules/versioning/ubuntu/readme.md @@ -1,3 +1,3 @@ Ubuntu versioning is used for Ubuntu container images that are referenced by their release version or a codename. -Versions to which this scheme applies are e.g. `22.04`, `jammy` and `jammy-20220815`. +Versions to which this scheme applies are e.g. `24.04`, `noble` and `noble-20240423`. diff --git a/lib/modules/versioning/unity3d/index.spec.ts b/lib/modules/versioning/unity3d/index.spec.ts new file mode 100644 index 00000000000000..9e9bd324ed7275 --- /dev/null +++ b/lib/modules/versioning/unity3d/index.spec.ts @@ -0,0 +1,64 @@ +import unity3d from '.'; + +describe('modules/versioning/unity3d/index', () => { + it.each` + input | expected + ${'9.0.3'} | ${false} + ${'1.2019.3.22'} | ${false} + ${'3.0.0-beta'} | ${false} + ${'2.0.2-pre20191018090318'} | ${false} + ${'1.0.0+c30d7625'} | ${false} + ${'2.3.4-beta+1990ef74'} | ${false} + ${'17.04'} | ${false} + ${'3.0.0.beta'} | ${false} + ${'5.1.2-+'} | ${false} + ${'2022.2.12f1 (1234567890ab)'} | ${true} + ${'2022.2.11 (1234567890ab)'} | ${false} + ${'2021.1.10p1 (1234567890ab)'} | ${true} + ${'2021.1.9b1 (1234567890ab)'} | ${true} + ${'2021.1.1a1 (1234567890ab)'} | ${true} + `('isValid("$input") === $expected', ({ input, expected }) => { + const res = !!unity3d.isValid(input); + expect(res).toBe(expected); + }); + + it.each` + input | expected + ${'2022.2.12f1 (1234567890ab)'} | ${true} + ${'2021.1.10p1 (1234567890ab)'} | ${false} + ${'2021.1.9b1 (1234567890ab)'} | ${false} + ${'2021.1.1a1 (1234567890ab)'} | ${false} + `('isStable("$input") === $expected', ({ input, expected }) => { + expect(unity3d.isStable(input)).toBe(expected); + }); + + it.each` + a | b | expected + ${'2022.2.12f1 (1234567890ab)'} | ${'2022.2.12f1 (1234567890ab)'} | ${true} + ${'2021.1.10p1 (1234567890ab)'} | ${'2021.1.10p1 (1234567890ab)'} | ${true} + ${'2021.1.9b1 (1234567890ab)'} | ${'2021.1.9b1 (1234567890ab)'} | ${true} + ${'2021.1.1a1 (1234567890ab)'} | ${'2021.1.1a1 (1234567890ab)'} | ${true} + `('equals($a, $b) === $expected', ({ a, b, expected }) => { + expect(unity3d.equals(a, b)).toBe(expected); + }); + + it.each` + a | b | expected + ${'2022.2.12f1 (1234567890ab)'} | ${'2022.2.12b1 (1234567890ab)'} | ${true} + ${'2022.2.12f1 (1234567890ab)'} | ${'2021.1.10p1 (1234567890ab)'} | ${true} + ${'2021.1.10p1 (1234567890ab)'} | ${'2021.1.9b1 (1234567890ab)'} | ${true} + ${'2021.1.9b1 (1234567890ab)'} | ${'2021.1.1a1 (1234567890ab)'} | ${true} + ${'2021.1.10p1 (1234567890ab)'} | ${'2022.2.12f1 (1234567890ab)'} | ${false} + ${'2021.1.9b1 (1234567890ab)'} | ${'2021.1.10p1 (1234567890ab)'} | ${false} + ${'2021.1.1a1 (1234567890ab)'} | ${'2021.1.9b1 (1234567890ab)'} | ${false} + ${'2022.2.12b1 (1234567890ab)'} | ${'2022.2.12f1 (1234567890ab)'} | ${false} + ${'2021.1.10p1 (1234567890ab)'} | ${'2022.2.12f1 (1234567890ab)'} | ${false} + ${'2021.1.9b1 (1234567890ab)'} | ${'2021.1.10p1 (1234567890ab)'} | ${false} + ${'2021.1.1a1 (1234567890ab)'} | ${'2021.1.9b1 (1234567890ab)'} | ${false} + ${'2022.2.12f1 (1234567890ab)'} | ${'2021.1.10p1 (1234567890ab)'} | ${true} + ${'2021.1.10p1 (1234567890ab)'} | ${'2021.1.9b1 (1234567890ab)'} | ${true} + ${'2021.1.9b1 (1234567890ab)'} | ${'2021.1.1a1 (1234567890ab)'} | ${true} + `('isGreaterThan($a, $b) === $expected', ({ a, b, expected }) => { + expect(unity3d.isGreaterThan(a, b)).toBe(expected); + }); +}); diff --git a/lib/modules/versioning/unity3d/index.ts b/lib/modules/versioning/unity3d/index.ts new file mode 100644 index 00000000000000..07d0e3284fab32 --- /dev/null +++ b/lib/modules/versioning/unity3d/index.ts @@ -0,0 +1,50 @@ +import { regEx } from '../../../util/regex'; +import { GenericVersion, GenericVersioningApi } from '../generic'; +import type { VersioningApi } from '../types'; + +export const id = 'unity3d'; +export const displayName = 'Unity3D'; +export const urls = [ + 'https://docs.unity3d.com/Manual/ScriptCompilationAssemblyDefinitionFiles.html#version-define-expressions', +]; +export const supportsRanges = false; + +class Unity3dVersioningApi extends GenericVersioningApi { + private static readonly parsingRegex = regEx( + /^(?\d+)\.(?\d+)\.(?\d+)(?\w)(?\d+)/, + ); + + private static readonly ReleaseStreamType = [ + 'a', // Alpha + 'b', // Beta + 'p', // Patch + 'x', // Experimental + 'f', // Stable + 'c', // Stable (China) + ]; + private static readonly stableVersions = ['f', 'c']; + + protected _parse(version: string): GenericVersion | null { + const matches = Unity3dVersioningApi.parsingRegex.exec(version); + if (!matches?.groups) { + return null; + } + const { major, minor, patch, releaseStream, build } = matches.groups; + + const release = [ + parseInt(major, 10), + parseInt(minor, 10), + parseInt(patch, 10), + Unity3dVersioningApi.ReleaseStreamType.indexOf(releaseStream), + parseInt(build, 10), + ]; + const isStable = + Unity3dVersioningApi.stableVersions.includes(releaseStream); + + return { release, prerelease: isStable ? undefined : build }; + } +} + +export const api: VersioningApi = new Unity3dVersioningApi(); + +export default api; diff --git a/lib/modules/versioning/unity3d/readme.md b/lib/modules/versioning/unity3d/readme.md new file mode 100644 index 00000000000000..537f80fc6123ed --- /dev/null +++ b/lib/modules/versioning/unity3d/readme.md @@ -0,0 +1,13 @@ +Unity versioning follow semantic versioning, followed by a letter, number and an optional hash: + +- Major version is the year of release +- Minor and patch version are incremental, starting at 0 +- The letter denotes a stream (**a**lpha, **b**eta, **f**inal release, etc.) +- The number is a growing index +- The hash is calculated by Unity internally and irrelevant for comparision + +Examples: + +- `2023.2.10f1 (316c1fd686f6)` +- `2023.3.0a17` +- `2023.3.0b4 (2cd31b2a2ee2)` diff --git a/lib/proxy.ts b/lib/proxy.ts index cb577b55c3f2ad..6cc1550ce08444 100644 --- a/lib/proxy.ts +++ b/lib/proxy.ts @@ -1,5 +1,6 @@ import is from '@sindresorhus/is'; import { createGlobalProxyAgent } from 'global-agent'; +import { logger } from './logger'; const envVars = ['HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY']; @@ -16,6 +17,7 @@ export function bootstrap(): void { } if (process.env[envVar]) { + logger.debug(`Detected ${envVar} value in env`); process.env[envVar.toLowerCase()] = process.env[envVar]; } }); diff --git a/lib/types/host-rules.ts b/lib/types/host-rules.ts index 38a571314a286d..23ac43d7f64cbd 100644 --- a/lib/types/host-rules.ts +++ b/lib/types/host-rules.ts @@ -1,4 +1,4 @@ -export interface HostRuleSearchResult { +export interface HostRule { authType?: string; token?: string; username?: string; @@ -20,11 +20,15 @@ export interface HostRuleSearchResult { httpsCertificateAuthority?: string; httpsPrivateKey?: string; httpsCertificate?: string; -} -export interface HostRule extends HostRuleSearchResult { encrypted?: HostRule; hostType?: string; matchHost?: string; resolvedHost?: string; + readOnly?: boolean; } + +export type CombinedHostRule = Omit< + HostRule, + 'encrypted' | 'hostType' | 'matchHost' | 'resolvedHost' | 'readOnly' +>; diff --git a/lib/types/index.ts b/lib/types/index.ts index 8c4a98d7ff625f..a111c9ade7ea7d 100644 --- a/lib/types/index.ts +++ b/lib/types/index.ts @@ -1,5 +1,5 @@ export type { CommitMessageJSON } from './commit-message-json'; -export type { HostRule, HostRuleSearchResult } from './host-rules'; +export type { HostRule, CombinedHostRule } from './host-rules'; export type { SkipReason } from './skip-reason'; export type { RangeStrategy } from './versioning'; export type { BranchStatus } from './branch-status'; diff --git a/lib/types/skip-reason.ts b/lib/types/skip-reason.ts index 5517d34c0c8563..2d2ff12a3379a2 100644 --- a/lib/types/skip-reason.ts +++ b/lib/types/skip-reason.ts @@ -26,6 +26,7 @@ export type SkipReason = | 'no-source' | 'non-hex-dep-types' | 'not-a-version' + | 'package-rules' | 'path-dependency' | 'placeholder-url' | 'unknown-engines' diff --git a/lib/util/array.ts b/lib/util/array.ts index 41b2ad0679c0af..485ef03fcfc3a5 100644 --- a/lib/util/array.ts +++ b/lib/util/array.ts @@ -7,10 +7,6 @@ export function coerceArray(input: T[] | null | undefined): T[] { return []; } -export function sortNumeric(a: number, b: number): number { - return a - b; -} - // Useful for filtering an array so that it includes values that are not null or // undefined. This predicate acts as a type guard so that the resulting type for // `values.filter(isNotNullOrUndefined)` is `T[]`. diff --git a/lib/util/cache/memory/types.ts b/lib/util/cache/memory/types.ts deleted file mode 100644 index c122e353b083f3..00000000000000 --- a/lib/util/cache/memory/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface LookupStats { - datasource: string; - duration: number; -} diff --git a/lib/util/cache/package/file.spec.ts b/lib/util/cache/package/file.spec.ts index 756162b194598c..faadaa2c809b00 100644 --- a/lib/util/cache/package/file.spec.ts +++ b/lib/util/cache/package/file.spec.ts @@ -30,8 +30,14 @@ describe('util/cache/package/file', () => { await set('_test-namespace', 'valid', 1234); await set('_test-namespace', 'expired', 1234, -5); await cacache.put(cacheFileName, 'invalid', 'not json'); + const expiredDigest = ( + await cacache.get(cacheFileName, '_test-namespace-expired') + ).integrity; await cleanup(); const entries = await cacache.ls(cacheFileName); expect(Object.keys(entries)).toEqual(['_test-namespace-valid']); + await expect( + cacache.get.byDigest(cacheFileName, expiredDigest), + ).rejects.toThrow('ENOENT'); }); }); diff --git a/lib/util/cache/package/file.ts b/lib/util/cache/package/file.ts index 9c57cb3ae8596b..b5b384ad03fad4 100644 --- a/lib/util/cache/package/file.ts +++ b/lib/util/cache/package/file.ts @@ -99,6 +99,8 @@ export async function cleanup(): Promise { deletedCount += 1; } } + logger.debug(`Verifying and cleaning cache: ${cacheFileName}`); + await cacache.verify(cacheFileName); const durationMs = Math.round(Date.now() - startTime); logger.debug( `Deleted ${deletedCount} of ${totalCount} file cached entries in ${durationMs}ms`, diff --git a/lib/util/cache/package/index.ts b/lib/util/cache/package/index.ts index 6151dee52c5f64..815d56d646a45d 100644 --- a/lib/util/cache/package/index.ts +++ b/lib/util/cache/package/index.ts @@ -1,4 +1,5 @@ import type { AllConfig } from '../../../config/types'; +import { PackageCacheStats } from '../../stats'; import * as memCache from '../memory'; import * as fileCache from './file'; import * as redisCache from './redis'; @@ -18,20 +19,17 @@ export async function get( if (!cacheProxy) { return undefined; } + const globalKey = getGlobalKey(namespace, key); - let start = 0; - if (memCache.get(globalKey) === undefined) { - memCache.set(globalKey, cacheProxy.get(namespace, key)); - start = Date.now(); - } - const result = await memCache.get(globalKey); - if (start) { - // Only count duration if it's not a duplicate - const durationMs = Math.round(Date.now() - start); - const cacheDurations = memCache.get('package-cache-gets') ?? []; - cacheDurations.push(durationMs); - memCache.set('package-cache-gets', cacheDurations); + let p = memCache.get(globalKey); + if (!p) { + p = PackageCacheStats.wrapGet(() => + cacheProxy!.get(namespace, key), + ); + memCache.set(globalKey, p); } + + const result = await p; return result; } @@ -44,14 +42,14 @@ export async function set( if (!cacheProxy) { return; } + + await PackageCacheStats.wrapSet(() => + cacheProxy!.set(namespace, key, value, minutes), + ); + const globalKey = getGlobalKey(namespace, key); - memCache.set(globalKey, value); - const start = Date.now(); - await cacheProxy.set(namespace, key, value, minutes); - const durationMs = Math.round(Date.now() - start); - const cacheDurations = memCache.get('package-cache-sets') ?? []; - cacheDurations.push(durationMs); - memCache.set('package-cache-sets', cacheDurations); + const p = Promise.resolve(value); + memCache.set(globalKey, p); } export async function init(config: AllConfig): Promise { diff --git a/lib/util/cache/package/sqlite.ts b/lib/util/cache/package/sqlite.ts index 486a6ac1048ac0..d13cab1864bd4e 100644 --- a/lib/util/cache/package/sqlite.ts +++ b/lib/util/cache/package/sqlite.ts @@ -1,9 +1,9 @@ import { promisify } from 'node:util'; import zlib, { constants } from 'node:zlib'; -import Sqlite from 'better-sqlite3'; import type { Database, Statement } from 'better-sqlite3'; import { exists } from 'fs-extra'; import * as upath from 'upath'; +import { sqlite } from '../../../expose.cjs'; import { logger } from '../../../logger'; import { ensureDir } from '../../fs'; import type { PackageCacheNamespace } from './types'; @@ -34,6 +34,8 @@ export class SqlitePackageCache { private readonly countStatement: Statement; static async init(cacheDir: string): Promise { + // simply let it throw if it fails, so no test coverage needed + const Sqlite = sqlite(); const sqliteDir = upath.join(cacheDir, 'renovate/renovate-cache-sqlite'); await ensureDir(sqliteDir); const sqliteFile = upath.join(sqliteDir, 'db.sqlite'); diff --git a/lib/util/cache/package/types.ts b/lib/util/cache/package/types.ts index b1e59a59f6ad3c..e301414c99be6e 100644 --- a/lib/util/cache/package/types.ts +++ b/lib/util/cache/package/types.ts @@ -45,6 +45,7 @@ export type PackageCacheNamespace = | 'datasource-deno-versions' | 'datasource-deno' | 'datasource-docker-architecture' + | 'datasource-docker-hub-cache' | 'datasource-docker-digest' | 'datasource-docker-hub-tags' | 'datasource-docker-imageconfig' @@ -89,6 +90,7 @@ export type PackageCacheNamespace = | 'datasource-packagist-public-files' | 'datasource-packagist' | 'datasource-pod' + | 'datasource-python-version' | 'datasource-releases' | 'datasource-repology-list' | 'datasource-ruby-version' @@ -100,6 +102,7 @@ export type PackageCacheNamespace = | 'datasource-terraform-provider-zip-hashes' | 'datasource-terraform-provider' | 'datasource-terraform' + | 'datasource-unity3d' | 'github-releases-datasource-v2' | 'github-tags-datasource-v2' | 'go' diff --git a/lib/util/cache/repository/common.ts b/lib/util/cache/repository/common.ts index 2846c28ceea852..2108a57e66a479 100644 --- a/lib/util/cache/repository/common.ts +++ b/lib/util/cache/repository/common.ts @@ -1,2 +1,13 @@ // Increment this whenever there could be incompatibilities between old and new cache structure +import upath from 'upath'; + export const CACHE_REVISION = 13; + +export function getLocalCacheFileName( + platform: string, + repository: string, +): string { + const repoCachePath = 'renovate/repository/'; + const fileName = `${repository}.json`; + return upath.join(repoCachePath, platform, fileName); +} diff --git a/lib/util/cache/repository/http-cache.spec.ts b/lib/util/cache/repository/http-cache.spec.ts new file mode 100644 index 00000000000000..ca22493aa033b9 --- /dev/null +++ b/lib/util/cache/repository/http-cache.spec.ts @@ -0,0 +1,75 @@ +import { DateTime, Settings } from 'luxon'; +import { GlobalConfig } from '../../../config/global'; +import { cleanupHttpCache } from './http-cache'; + +describe('util/cache/repository/http-cache', () => { + beforeEach(() => { + const now = DateTime.fromISO('2024-04-12T12:00:00.000Z').valueOf(); + Settings.now = () => now; + GlobalConfig.reset(); + }); + + it('should not throw if cache is not a valid HttpCache', () => { + expect(() => cleanupHttpCache({})).not.toThrow(); + }); + + it('should remove expired items from the cache', () => { + const now = DateTime.now(); + const expiredItemTimestamp = now.minus({ days: 91 }).toISO(); + const cache = { + httpCache: { + 'http://example.com/foo': { + timestamp: expiredItemTimestamp, + etag: 'abc', + lastModified: 'Mon, 01 Jan 2024 00:00:00 GMT', + httpResponse: {}, + }, + 'http://example.com/bar': { + timestamp: now.toISO(), + etag: 'abc', + lastModified: 'Mon, 01 Jan 2024 00:00:00 GMT', + httpResponse: {}, + }, + }, + }; + + cleanupHttpCache(cache); + + expect(cache).toEqual({ + httpCache: { + 'http://example.com/bar': { + timestamp: now.toISO(), + etag: 'abc', + httpResponse: {}, + lastModified: 'Mon, 01 Jan 2024 00:00:00 GMT', + }, + }, + }); + }); + + it('should remove all items if ttlDays is not configured', () => { + GlobalConfig.set({ httpCacheTtlDays: 0 }); + + const now = DateTime.now(); + const cache = { + httpCache: { + 'http://example.com/foo': { + timestamp: now.toISO(), + etag: 'abc', + lastModified: 'Mon, 01 Jan 2024 00:00:00 GMT', + httpResponse: {}, + }, + 'http://example.com/bar': { + timestamp: now.toISO(), + etag: 'abc', + lastModified: 'Mon, 01 Jan 2024 00:00:00 GMT', + httpResponse: {}, + }, + }, + }; + + cleanupHttpCache(cache); + + expect(cache).toEqual({}); + }); +}); diff --git a/lib/util/cache/repository/http-cache.ts b/lib/util/cache/repository/http-cache.ts new file mode 100644 index 00000000000000..c31eb84cdb390d --- /dev/null +++ b/lib/util/cache/repository/http-cache.ts @@ -0,0 +1,33 @@ +import { DateTime } from 'luxon'; +import { GlobalConfig } from '../../../config/global'; +import { logger } from '../../../logger'; +import { HttpCacheSchema } from '../../http/cache/schema'; +import type { RepoCacheData } from './types'; + +export function cleanupHttpCache(cacheData: RepoCacheData): void { + const { httpCache } = cacheData; + if (!httpCache) { + logger.trace('cleanupHttpCache: no http cache to clean up'); + return; + } + + const ttlDays = GlobalConfig.get('httpCacheTtlDays', 90); + if (ttlDays === 0) { + logger.trace('cleanupHttpCache: zero value received, removing the cache'); + delete cacheData['httpCache']; + return; + } + + const now = DateTime.now(); + for (const [url, item] of Object.entries(httpCache)) { + const parsed = HttpCacheSchema.safeParse(item); + if (parsed.success && parsed.data) { + const item = parsed.data; + const expiry = DateTime.fromISO(item.timestamp).plus({ days: ttlDays }); + if (expiry < now) { + logger.debug(`http cache: removing expired cache for ${url}`); + delete httpCache[url]; + } + } + } +} diff --git a/lib/util/cache/repository/impl/base.ts b/lib/util/cache/repository/impl/base.ts index 0a74accb69e197..b85d6ba3ee6d97 100644 --- a/lib/util/cache/repository/impl/base.ts +++ b/lib/util/cache/repository/impl/base.ts @@ -5,6 +5,7 @@ import { compressToBase64, decompressFromBase64 } from '../../../compress'; import { hash } from '../../../hash'; import { safeStringify } from '../../../stringify'; import { CACHE_REVISION } from '../common'; +import { cleanupHttpCache } from '../http-cache'; import { RepoCacheRecord, RepoCacheV13 } from '../schema'; import type { RepoCache, RepoCacheData } from '../types'; @@ -63,13 +64,14 @@ export abstract class RepoCacheBase implements RepoCache { return; } - logger.debug('Repository cache is invalid'); + logger.warn({ err: cacheV13.error }, 'Repository cache is invalid'); } catch (err) /* istanbul ignore next: not easily testable */ { logger.debug({ err }, 'Error reading repository cache'); } } async save(): Promise { + cleanupHttpCache(this.data); const jsonStr = safeStringify(this.data); const hashedJsonStr = hash(jsonStr); if (hashedJsonStr === this.oldHash) { diff --git a/lib/util/cache/repository/impl/local.ts b/lib/util/cache/repository/impl/local.ts index 530dbabc80243d..961983c5a31916 100644 --- a/lib/util/cache/repository/impl/local.ts +++ b/lib/util/cache/repository/impl/local.ts @@ -1,6 +1,6 @@ -import upath from 'upath'; import { logger } from '../../../../logger'; import { cachePathExists, outputCacheFile, readCacheFile } from '../../../fs'; +import { getLocalCacheFileName } from '../common'; import type { RepoCacheRecord } from '../schema'; import { RepoCacheBase } from './base'; @@ -29,9 +29,6 @@ export class RepoCacheLocal extends RepoCacheBase { } private getCacheFileName(): string { - const repoCachePath = 'renovate/repository/'; - const platform = this.platform; - const fileName = `${this.repository}.json`; - return upath.join(repoCachePath, platform, fileName); + return getLocalCacheFileName(this.platform, this.repository); } } diff --git a/lib/util/cache/repository/impl/s3.spec.ts b/lib/util/cache/repository/impl/s3.spec.ts index 69838e09301765..438d2316a14e8b 100644 --- a/lib/util/cache/repository/impl/s3.spec.ts +++ b/lib/util/cache/repository/impl/s3.spec.ts @@ -8,7 +8,7 @@ import { S3Client, } from '@aws-sdk/client-s3'; import { mockClient } from 'aws-sdk-client-mock'; -import { partial } from '../../../../../test/util'; +import { fs, partial } from '../../../../../test/util'; import { GlobalConfig } from '../../../../config/global'; import { logger } from '../../../../logger'; import { parseS3Url } from '../../../s3'; @@ -16,6 +16,8 @@ import type { RepoCacheRecord } from '../schema'; import { CacheFactory } from './cache-factory'; import { RepoCacheS3 } from './s3'; +jest.mock('../../../fs'); + function createGetObjectCommandInput( repository: string, url: string, @@ -57,7 +59,7 @@ describe('util/cache/repository/impl/s3', () => { let s3Cache: RepoCacheS3; beforeEach(() => { - GlobalConfig.set({ platform: 'github' }); + GlobalConfig.set({ cacheDir: '/tmp/cache', platform: 'github' }); s3Mock.reset(); s3Cache = new RepoCacheS3(repository, '0123456789abcdef', url); getObjectCommandInput = createGetObjectCommandInput(repository, url); @@ -196,4 +198,19 @@ describe('util/cache/repository/impl/s3', () => { const cache = CacheFactory.get(repository, '0123456789abcdef', url); expect(cache instanceof RepoCacheS3).toBeTrue(); }); + + it('should persists data locally after uploading to s3', async () => { + process.env.RENOVATE_X_REPO_CACHE_FORCE_LOCAL = 'true'; + const putObjectCommandOutput: PutObjectCommandOutput = { + $metadata: { attempts: 1, httpStatusCode: 200, totalRetryDelay: 0 }, + }; + s3Mock + .on(PutObjectCommand, putObjectCommandInput) + .resolvesOnce(putObjectCommandOutput); + await s3Cache.write(repoCache); + expect(fs.outputCacheFile).toHaveBeenCalledWith( + 'renovate/repository/github/org/repo.json', + JSON.stringify(repoCache), + ); + }); }); diff --git a/lib/util/cache/repository/impl/s3.ts b/lib/util/cache/repository/impl/s3.ts index 407ea3664f59b4..e0cbe20bbb6451 100644 --- a/lib/util/cache/repository/impl/s3.ts +++ b/lib/util/cache/repository/impl/s3.ts @@ -5,9 +5,12 @@ import { PutObjectCommand, PutObjectCommandInput, } from '@aws-sdk/client-s3'; +import is from '@sindresorhus/is'; import { logger } from '../../../../logger'; +import { outputCacheFile } from '../../../fs'; import { getS3Client, parseS3Url } from '../../../s3'; import { streamToString } from '../../../streams'; +import { getLocalCacheFileName } from '../common'; import type { RepoCacheRecord } from '../schema'; import { RepoCacheBase } from './base'; @@ -54,14 +57,22 @@ export class RepoCacheS3 extends RepoCacheBase { async write(data: RepoCacheRecord): Promise { const cacheFileName = this.getCacheFileName(); + const stringifiedCache = JSON.stringify(data); const s3Params: PutObjectCommandInput = { Bucket: this.bucket, Key: cacheFileName, - Body: JSON.stringify(data), + Body: stringifiedCache, ContentType: 'application/json', }; try { await this.s3Client.send(new PutObjectCommand(s3Params)); + if (is.nonEmptyString(process.env.RENOVATE_X_REPO_CACHE_FORCE_LOCAL)) { + const cacheLocalFileName = getLocalCacheFileName( + this.platform, + this.repository, + ); + await outputCacheFile(cacheLocalFileName, stringifiedCache); + } } catch (err) { logger.warn({ err }, 'RepoCacheS3.write() - failure'); } diff --git a/lib/util/cache/repository/types.ts b/lib/util/cache/repository/types.ts index d34303176a2f21..3341418e23064e 100644 --- a/lib/util/cache/repository/types.ts +++ b/lib/util/cache/repository/types.ts @@ -4,11 +4,8 @@ import type { UpdateType, } from '../../../config/types'; import type { PackageFile } from '../../../modules/manager/types'; -import type { BitbucketPrCacheData } from '../../../modules/platform/bitbucket/types'; -import type { GiteaPrCacheData } from '../../../modules/platform/gitea/types'; import type { RepoInitConfig } from '../../../workers/repository/init/types'; import type { PrBlockedBy } from '../../../workers/types'; -import type { HttpResponse } from '../../http/types'; export interface BaseBranchCache { sha: string; // branch commit sha @@ -85,7 +82,7 @@ export interface BranchCache { */ branchName: string; /** - * Whether the update branch is behind base branh + * Whether the update branch is behind base branch */ isBehindBase?: boolean; /** @@ -125,16 +122,9 @@ export interface BranchCache { result?: string; } -export interface HttpCache { - etag?: string; - httpResponse: HttpResponse; - lastModified?: string; - timeStamp: string; -} - export interface RepoCacheData { configFileName?: string; - httpCache?: Record; + httpCache?: Record; semanticCommits?: 'enabled' | 'disabled'; branches?: BranchCache[]; init?: RepoInitConfig; @@ -142,11 +132,18 @@ export interface RepoCacheData { lastPlatformAutomergeFailure?: string; platform?: { gitea?: { - pullRequestsCache?: GiteaPrCacheData; + pullRequestsCache?: unknown; + }; + github?: { + /** + * To avoid circular dependency problem, we use `unknown` type here. + */ + pullRequestsCache?: unknown; + graphqlPageCache?: unknown; + issuesCache?: Record; }; - github?: Record; bitbucket?: { - pullRequestsCache?: BitbucketPrCacheData; + pullRequestsCache?: unknown; }; }; prComments?: Record>; diff --git a/lib/util/check-token.ts b/lib/util/check-token.ts index 48cf641bd9602c..996b73f1ee7276 100644 --- a/lib/util/check-token.ts +++ b/lib/util/check-token.ts @@ -4,7 +4,7 @@ import { GithubReleaseAttachmentsDatasource } from '../modules/datasource/github import { GithubReleasesDatasource } from '../modules/datasource/github-releases'; import { GithubTagsDatasource } from '../modules/datasource/github-tags'; import type { PackageFileContent } from '../modules/manager/types'; -import type { HostRuleSearchResult } from '../types'; +import type { CombinedHostRule } from '../types'; import * as memCache from '../util/cache/memory'; import * as hostRules from './host-rules'; @@ -73,7 +73,7 @@ export function isGithubFineGrainedPersonalAccessToken(token: string): boolean { } export function findGithubToken( - searchResult: HostRuleSearchResult, + searchResult: CombinedHostRule, ): string | undefined { return searchResult?.token?.replace('x-access-token:', ''); } diff --git a/lib/util/emoji.spec.ts b/lib/util/emoji.spec.ts index 97e9c9309bc1eb..9dc475a838ae05 100644 --- a/lib/util/emoji.spec.ts +++ b/lib/util/emoji.spec.ts @@ -21,6 +21,11 @@ describe('util/emoji', () => { expect(emojify(':foo: :bar: :bee:')).toBe(':foo: :bar: 🐝'); }); + it('convert warning shortcode to emoji', () => { + const warning = emojify(':warning:'); + expect(warning).toBe('⚠️'); + }); + it('does not encode when config option is disabled', () => { setEmojiConfig({ unicodeEmoji: false }); expect(emojify('Let it :bee:')).toBe('Let it :bee:'); @@ -54,20 +59,30 @@ describe('util/emoji', () => { expect(unemojify(unsupported)).toBe('�'); }); }); - }); - describe('problem characters', () => { - it.each(['🚀', '💎', '🧹', '📦'])('converts %s forth and back', (char) => { + it('converts warning emoji to shortcode', () => { setEmojiConfig({ unicodeEmoji: false }); - const codified = unemojify(char); - expect(codified).not.toEqual(char); - - setEmojiConfig({ unicodeEmoji: true }); - const emojified = emojify(codified); - expect(emojified).toEqual(char); + const emoji = '⚠️'; + const result = unemojify(emoji); + expect(result).toBe(':warning:'); }); }); + describe('problematic characters', () => { + it.each(['🚀', '💎', '🧹', '📦', '⚠️'])( + 'converts %s forth and back', + (char) => { + setEmojiConfig({ unicodeEmoji: false }); + const codified = unemojify(char); + expect(codified).not.toEqual(char); + + setEmojiConfig({ unicodeEmoji: true }); + const emojified = emojify(codified); + expect(emojified).toEqual(char); + }, + ); + }); + describe('stripEmojis', () => { const makeEmoji = (hexCode: string): string => fromCodepointToUnicode(fromHexcodeToCodepoint(hexCode)); diff --git a/lib/util/emoji.ts b/lib/util/emoji.ts index 219392ab98b77d..a5e03a86409cac 100644 --- a/lib/util/emoji.ts +++ b/lib/util/emoji.ts @@ -1,4 +1,3 @@ -import is from '@sindresorhus/is'; import mathiasBynensEmojiRegex from 'emoji-regex'; import { fromCodepointToUnicode, @@ -12,6 +11,7 @@ import type { RenovateConfig } from '../config/types'; import dataFiles from '../data-files.generated'; import { logger } from '../logger'; import { regEx } from './regex'; +import { Result } from './result'; import { Json } from './schema-utils'; let unicodeEmoji = true; @@ -21,27 +21,48 @@ const shortCodesByHex = new Map(); const hexCodesByShort = new Map(); const EmojiShortcodesSchema = Json.pipe( - z.record(z.string(), z.union([z.string(), z.array(z.string())])), + z.record( + z.string(), + z.union([z.string().transform((val) => [val]), z.array(z.string())]), + ), ); +type EmojiShortcodeMapping = z.infer; + +const patchedEmojis: EmojiShortcodeMapping = { + '26A0-FE0F': ['warning'], // Colorful warning (⚠️) instead of black and white (⚠) +}; + +function initMapping(mapping: EmojiShortcodeMapping): void { + for (const [hex, shortcodes] of Object.entries(mapping)) { + const mainShortcode = `:${shortcodes[0]}:`; + + shortCodesByHex.set(hex, mainShortcode); + shortCodesByHex.set(stripHexCode(hex), mainShortcode); + + for (const shortcode of shortcodes) { + hexCodesByShort.set(`:${shortcode}:`, hex); + } + } +} function lazyInitMappings(): void { if (!mappingsInitialized) { - const result = EmojiShortcodesSchema.safeParse( - dataFiles.get('node_modules/emojibase-data/en/shortcodes/github.json')!, + const githubShortcodes = dataFiles.get( + 'node_modules/emojibase-data/en/shortcodes/github.json', ); - // istanbul ignore if: not easily testable - if (!result.success) { - logger.warn({ error: result.error }, 'Unable to parse emoji shortcodes'); - return; - } - for (const [hex, val] of Object.entries(result.data)) { - const shortCodes = is.array(val) ? val : [val]; - shortCodesByHex.set(hex, `:${shortCodes[0]}:`); - shortCodes.forEach((shortCode) => { - hexCodesByShort.set(`:${shortCode}:`, hex); - }); - } - mappingsInitialized = true; + + Result.parse(githubShortcodes, EmojiShortcodesSchema) + .onValue((data) => { + initMapping(data); + initMapping(patchedEmojis); + mappingsInitialized = true; + }) + .onError( + /* istanbul ignore next */ + (error) => { + logger.warn({ error }, 'Unable to parse emoji shortcodes'); + }, + ); } } diff --git a/lib/util/exec/containerbase.ts b/lib/util/exec/containerbase.ts index 73111067539ba1..1b5585effc6410 100644 --- a/lib/util/exec/containerbase.ts +++ b/lib/util/exec/containerbase.ts @@ -196,6 +196,11 @@ const allToolConfig: Record = { packageName: 'flutter', versioning: npmVersioningId, }, + vendir: { + datasource: 'github-releases', + packageName: 'carvel-dev/vendir', + versioning: semverVersioningId, + }, }; let _getPkgReleases: Promise | null = diff --git a/lib/util/exec/env.ts b/lib/util/exec/env.ts index cf53afd966df60..2bf84be36ca372 100644 --- a/lib/util/exec/env.ts +++ b/lib/util/exec/env.ts @@ -29,7 +29,9 @@ const basicEnvVars = [ 'COREPACK_ENABLE_NETWORK', 'COREPACK_ENABLE_STRICT', 'COREPACK_ENABLE_PROJECT_SPEC', + 'COREPACK_ENABLE_UNSAFE_CUSTOM_URLS', 'COREPACK_HOME', + 'COREPACK_INTEGRITY_KEYS', 'COREPACK_NPM_REGISTRY', 'COREPACK_NPM_TOKEN', 'COREPACK_NPM_USERNAME', diff --git a/lib/util/fs/index.spec.ts b/lib/util/fs/index.spec.ts index 13c175716895f7..7a7fcb8f0b13c8 100644 --- a/lib/util/fs/index.spec.ts +++ b/lib/util/fs/index.spec.ts @@ -6,6 +6,7 @@ import { mockedFunction } from '../../../test/util'; import { GlobalConfig } from '../../config/global'; import { cachePathExists, + cachePathIsFile, chmodLocalFile, createCacheWriteStream, deleteLocalFile, @@ -33,6 +34,7 @@ import { rmCache, statLocalFile, writeLocalFile, + writeSystemFile, } from '.'; jest.mock('../exec/env'); @@ -453,6 +455,12 @@ describe('util/fs/index', () => { }); }); + describe('cachePathIsFile', () => { + it('returns false if does not exist', async () => { + await expect(cachePathIsFile(`a/a/file.txt`)).resolves.toBe(false); + }); + }); + describe('readCacheFile', () => { it('reads file', async () => { await fs.outputFile(`${cacheDir}/foo/bar/file.txt`, 'foobar'); @@ -480,6 +488,14 @@ describe('util/fs/index', () => { }); }); + describe('writeSystemFile', () => { + it('writes file', async () => { + const path = `${tmpDir}/file.txt`; + await writeSystemFile(path, 'foobar'); + expect(await readSystemFile(path)).toEqual(Buffer.from('foobar')); + }); + }); + describe('getLocalFiles', () => { it('reads list of files from local fs', async () => { const fileContentMap = { diff --git a/lib/util/fs/index.ts b/lib/util/fs/index.ts index 0d34ea3a0931a9..64512d6257a546 100644 --- a/lib/util/fs/index.ts +++ b/lib/util/fs/index.ts @@ -249,9 +249,15 @@ export async function statLocalFile( } } -export function listCacheDir(path: string): Promise { +export function listCacheDir( + path: string, + options: { recursive: boolean } = { recursive: false }, +): Promise { const fullPath = ensureCachePath(path); - return fs.readdir(fullPath); + return fs.readdir(fullPath, { + encoding: 'utf-8', + recursive: options.recursive, + }); } export async function rmCache(path: string): Promise { @@ -269,6 +275,16 @@ export async function cachePathExists(pathName: string): Promise { } } +export async function cachePathIsFile(pathName: string): Promise { + const path = ensureCachePath(pathName); + try { + const s = await fs.stat(path); + return s.isFile(); + } catch (e) { + return false; + } +} + export async function readCacheFile(fileName: string): Promise; export async function readCacheFile( fileName: string, @@ -302,6 +318,13 @@ export function readSystemFile( return encoding ? fs.readFile(fileName, encoding) : fs.readFile(fileName); } +export async function writeSystemFile( + fileName: string, + data: string | Buffer, +): Promise { + await fs.outputFile(fileName, data); +} + export async function getLocalFiles( fileNames: string[], ): Promise> { diff --git a/lib/util/fs/util.ts b/lib/util/fs/util.ts index cbe8afae07824b..2a15c048a0f8a0 100644 --- a/lib/util/fs/util.ts +++ b/lib/util/fs/util.ts @@ -3,11 +3,11 @@ import { GlobalConfig } from '../../config/global'; import { FILE_ACCESS_VIOLATION_ERROR } from '../../constants/error-messages'; import { logger } from '../../logger'; -function assertBaseDir(path: string, baseDir: string): void { - if (!path.startsWith(baseDir)) { +function assertBaseDir(path: string, allowedDir: string): void { + if (!path.startsWith(allowedDir)) { logger.debug( - { path, baseDir }, - 'Preventing access to file outside the base directory', + { path, allowedDir }, + 'Preventing access to file outside allowed directory', ); throw new Error(FILE_ACCESS_VIOLATION_ERROR); } diff --git a/lib/util/git/auth.ts b/lib/util/git/auth.ts index d22abd17163412..c776b3c4831031 100644 --- a/lib/util/git/auth.ts +++ b/lib/util/git/auth.ts @@ -4,7 +4,7 @@ import type { HostRule } from '../../types'; import { detectPlatform } from '../common'; import { find, getAll } from '../host-rules'; import { regEx } from '../regex'; -import { createURLFromHostOrURL, validateUrl } from '../url'; +import { createURLFromHostOrURL, isHttpUrl } from '../url'; import type { AuthenticationRule } from './types'; import { parseGitUrl } from './url'; @@ -203,7 +203,7 @@ function addAuthFromHostRule( ): NodeJS.ProcessEnv { let environmentVariables = env; const httpUrl = createURLFromHostOrURL(hostRule.matchHost!)?.toString(); - if (validateUrl(httpUrl)) { + if (isHttpUrl(httpUrl)) { logger.trace(`Adding Git authentication for ${httpUrl} using token auth.`); environmentVariables = getGitAuthenticatedEnvironmentVariables( httpUrl!, diff --git a/lib/util/git/index.ts b/lib/util/git/index.ts index 0cad95457e4b7e..5900222c072c11 100644 --- a/lib/util/git/index.ts +++ b/lib/util/git/index.ts @@ -585,7 +585,7 @@ export async function getFileList(): Promise { } export function getBranchList(): string[] { - return Object.keys(config.branchCommits); + return Object.keys(config.branchCommits ?? /* istanbul ignore next */ {}); } export async function isBranchBehindBase( diff --git a/lib/util/git/url.spec.ts b/lib/util/git/url.spec.ts index 2656d536c6e94c..efddf1c48949c4 100644 --- a/lib/util/git/url.spec.ts +++ b/lib/util/git/url.spec.ts @@ -49,6 +49,14 @@ describe('util/git/url', () => { expect(getHttpUrl('http://foo.bar/')).toBe('http://foo.bar/'); }); + it('returns http url for ssh url with port', () => { + expect( + getHttpUrl( + 'ssh://git@gitlab.example.com:22222/typo3-extensions/poll-pro.git', + ), + ).toBe('https://gitlab.example.com/typo3-extensions/poll-pro.git'); + }); + it('returns gitlab url with token', () => { expect(getHttpUrl('http://gitlab.com/', 'token')).toBe( 'http://gitlab-ci-token:token@gitlab.com/', diff --git a/lib/util/git/url.ts b/lib/util/git/url.ts index 0b573a7dac1d08..e51dff0a792c57 100644 --- a/lib/util/git/url.ts +++ b/lib/util/git/url.ts @@ -11,9 +11,13 @@ export function parseGitUrl(url: string): gitUrlParse.GitUrl { export function getHttpUrl(url: string, token?: string): string { const parsedUrl = parseGitUrl(url); - const protocol = regEx(/^https?$/).exec(parsedUrl.protocol) - ? parsedUrl.protocol - : 'https'; + let { protocol } = parsedUrl; + + // Convert non-https URLs to https and strip port + if (!regEx(/^https?$/).test(protocol)) { + parsedUrl.port = 443; + protocol = 'https'; + } parsedUrl.user = ''; parsedUrl.token = token ?? ''; diff --git a/lib/util/github/graphql/datasource-fetcher.ts b/lib/util/github/graphql/datasource-fetcher.ts index b32995881c262e..f8fe2d47a90e5b 100644 --- a/lib/util/github/graphql/datasource-fetcher.ts +++ b/lib/util/github/graphql/datasource-fetcher.ts @@ -107,6 +107,7 @@ export class GithubGraphqlDatasourceFetcher< return { baseUrl, repository, + readOnly: true, body: { query, variables }, }; } diff --git a/lib/util/host-rules.spec.ts b/lib/util/host-rules.spec.ts index a16a9332e3d29c..f5fa0cbf239a53 100644 --- a/lib/util/host-rules.spec.ts +++ b/lib/util/host-rules.spec.ts @@ -295,6 +295,25 @@ describe('util/host-rules', () => { }), ).toEqual({ token: 'longest' }); }); + + it('matches readOnly requests', () => { + add({ + matchHost: 'https://api.github.com/repos/', + token: 'aaa', + hostType: 'github', + }); + add({ + matchHost: 'https://api.github.com', + token: 'bbb', + readOnly: true, + }); + expect( + find({ + url: 'https://api.github.com/repos/foo/bar/tags', + readOnly: true, + }), + ).toEqual({ token: 'bbb' }); + }); }); describe('hosts()', () => { diff --git a/lib/util/host-rules.ts b/lib/util/host-rules.ts index 19e3addbb0444b..1da59fcd9e3c49 100644 --- a/lib/util/host-rules.ts +++ b/lib/util/host-rules.ts @@ -1,11 +1,10 @@ import is from '@sindresorhus/is'; -import merge from 'deepmerge'; import { logger } from '../logger'; -import type { HostRule, HostRuleSearchResult } from '../types'; +import type { CombinedHostRule, HostRule } from '../types'; import { clone } from './clone'; import * as sanitize from './sanitize'; import { toBase64 } from './string'; -import { parseUrl, validateUrl } from './url'; +import { isHttpUrl, parseUrl } from './url'; let hostRules: HostRule[] = []; @@ -74,94 +73,111 @@ export function add(params: HostRule): void { export interface HostRuleSearch { hostType?: string; url?: string; + readOnly?: boolean; } -function isEmptyRule(rule: HostRule): boolean { - return !rule.hostType && !rule.resolvedHost; -} +function matchesHost(url: string, matchHost: string): boolean { + if (isHttpUrl(url) && isHttpUrl(matchHost)) { + return url.startsWith(matchHost); + } -function isHostTypeRule(rule: HostRule): boolean { - return !!rule.hostType && !rule.resolvedHost; -} + const parsedUrl = parseUrl(url); + if (!parsedUrl) { + return false; + } -function isHostOnlyRule(rule: HostRule): boolean { - return !rule.hostType && !!rule.matchHost; -} + const { hostname } = parsedUrl; + if (!hostname) { + return false; + } -function isMultiRule(rule: HostRule): boolean { - return !!rule.hostType && !!rule.resolvedHost; + if (hostname === matchHost) { + return true; + } + + const topLevelSuffix = matchHost.startsWith('.') + ? matchHost + : `.${matchHost}`; + return hostname.endsWith(topLevelSuffix); } -function matchesHostType(rule: HostRule, search: HostRuleSearch): boolean { - return rule.hostType === search.hostType; +function fromShorterToLongerMatchHost(a: HostRule, b: HostRule): number { + if (!a.matchHost || !b.matchHost) { + return 0; + } + return a.matchHost.length - b.matchHost.length; } -function matchesHost(rule: HostRule, search: HostRuleSearch): boolean { - // istanbul ignore if - if (!rule.matchHost) { - return false; +function hostRuleRank({ hostType, matchHost, readOnly }: HostRule): number { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + if ((hostType || readOnly) && matchHost) { + return 3; } - if (search.url && validateUrl(rule.matchHost)) { - return search.url.startsWith(rule.matchHost); + + if (matchHost) { + return 2; } - const parsedUrl = search.url ? parseUrl(search.url) : null; - if (!parsedUrl?.hostname) { - return false; + + if (hostType) { + return 1; } - const { hostname } = parsedUrl; - const dotPrefixedMatchHost = rule.matchHost.startsWith('.') - ? rule.matchHost - : `.${rule.matchHost}`; - return hostname === rule.matchHost || hostname.endsWith(dotPrefixedMatchHost); + + return 0; } -function prioritizeLongestMatchHost(rule1: HostRule, rule2: HostRule): number { - // istanbul ignore if: won't happen in practice - if (!rule1.matchHost || !rule2.matchHost) { - return 0; - } - return rule1.matchHost.length - rule2.matchHost.length; +function fromLowerToHigherRank(a: HostRule, b: HostRule): number { + return hostRuleRank(a) - hostRuleRank(b); } -export function find(search: HostRuleSearch): HostRuleSearchResult { - if (!(!!search.hostType || search.url)) { +export function find(search: HostRuleSearch): CombinedHostRule { + if ([search.hostType, search.url].every(is.falsy)) { logger.warn({ search }, 'Invalid hostRules search'); return {}; } - let res: HostRule = {}; - // First, apply empty rule matches - hostRules - .filter((rule) => isEmptyRule(rule)) - .forEach((rule) => { - res = merge(res, rule); - }); - // Next, find hostType-only matches - hostRules - .filter((rule) => isHostTypeRule(rule) && matchesHostType(rule, search)) - .forEach((rule) => { - res = merge(res, rule); - }); - hostRules - .filter((rule) => isHostOnlyRule(rule) && matchesHost(rule, search)) - .sort(prioritizeLongestMatchHost) - .forEach((rule) => { - res = merge(res, rule); - }); - // Finally, find combination matches - hostRules - .filter( - (rule) => - isMultiRule(rule) && - matchesHostType(rule, search) && - matchesHost(rule, search), - ) - .sort(prioritizeLongestMatchHost) - .forEach((rule) => { - res = merge(res, rule); - }); + + // Sort primarily by rank, and secondarily by matchHost length + const sortedRules = hostRules + .sort(fromShorterToLongerMatchHost) + .sort(fromLowerToHigherRank); + + const matchedRules: HostRule[] = []; + for (const rule of sortedRules) { + let hostTypeMatch = true; + let hostMatch = true; + let readOnlyMatch = true; + + if (rule.hostType) { + hostTypeMatch = false; + if (search.hostType === rule.hostType) { + hostTypeMatch = true; + } + } + + if (rule.matchHost && rule.resolvedHost) { + hostMatch = false; + if (search.url) { + hostMatch = matchesHost(search.url, rule.matchHost); + } + } + + if (!is.undefined(rule.readOnly)) { + readOnlyMatch = false; + if (search.readOnly === rule.readOnly) { + readOnlyMatch = true; + hostTypeMatch = true; // When we match `readOnly`, we don't care about `hostType` + } + } + + if (hostTypeMatch && readOnlyMatch && hostMatch) { + matchedRules.push(clone(rule)); + } + } + + const res: HostRule = Object.assign({}, ...matchedRules); delete res.hostType; delete res.resolvedHost; delete res.matchHost; + delete res.readOnly; return res; } @@ -175,8 +191,8 @@ export function hosts({ hostType }: { hostType: string }): string[] { export function hostType({ url }: { url: string }): string | null { return ( hostRules - .filter((rule) => matchesHost(rule, { url })) - .sort(prioritizeLongestMatchHost) + .filter((rule) => rule.matchHost && matchesHost(url, rule.matchHost)) + .sort(fromShorterToLongerMatchHost) .map((rule) => rule.hostType) .filter(is.truthy) .pop() ?? null diff --git a/lib/util/http/auth.spec.ts b/lib/util/http/auth.spec.ts index fe49fa57fc2ade..2854af307c1389 100644 --- a/lib/util/http/auth.spec.ts +++ b/lib/util/http/auth.spec.ts @@ -189,6 +189,30 @@ describe('util/http/auth', () => { token: 'test', }); }); + + it(`honors authType`, () => { + const opts: GotOptions = { + headers: {}, + token: 'test', + context: { + authType: 'Bearer', + }, + hostType: 'custom', + }; + + applyAuthorization(opts); + + expect(opts).toEqual({ + context: { + authType: 'Bearer', + }, + headers: { + authorization: 'Bearer test', + }, + hostType: 'custom', + token: 'test', + }); + }); }); describe('removeAuthorization', () => { diff --git a/lib/util/http/auth.ts b/lib/util/http/auth.ts index d0fb77de4ad6c7..99cc9bec817651 100644 --- a/lib/util/http/auth.ts +++ b/lib/util/http/auth.ts @@ -29,7 +29,14 @@ export function applyAuthorization( options.headers ??= {}; if (options.token) { - if ( + const authType = options.context?.authType; + if (authType) { + if (authType === 'Token-Only') { + options.headers.authorization = options.token; + } else { + options.headers.authorization = `${authType} ${options.token}`; + } + } else if ( options.hostType && GITEA_API_USING_HOST_TYPES.includes(options.hostType) ) { @@ -61,14 +68,7 @@ export function applyAuthorization( options.headers.authorization = `Bearer ${options.token}`; } } else { - // Custom Auth type, eg `Basic XXXX_TOKEN` - const type = options.context?.authType ?? 'Bearer'; - - if (type === 'Token-Only') { - options.headers.authorization = options.token; - } else { - options.headers.authorization = `${type} ${options.token}`; - } + options.headers.authorization = `Bearer ${options.token}`; } delete options.token; } else if (options.password !== undefined) { diff --git a/lib/util/http/bitbucket-server.ts b/lib/util/http/bitbucket-server.ts index 463e37d3c007ed..2189e8188a8fc5 100644 --- a/lib/util/http/bitbucket-server.ts +++ b/lib/util/http/bitbucket-server.ts @@ -1,10 +1,5 @@ import { resolveBaseUrl } from '../url'; -import type { - HttpOptions, - HttpRequestOptions, - HttpResponse, - InternalHttpOptions, -} from './types'; +import type { HttpOptions, HttpResponse, InternalHttpOptions } from './types'; import { Http } from '.'; let baseUrl: string; @@ -19,7 +14,7 @@ export class BitbucketServerHttp extends Http { protected override request( path: string, - options?: InternalHttpOptions & HttpRequestOptions, + options?: InternalHttpOptions, ): Promise> { const url = resolveBaseUrl(baseUrl, path); const opts = { diff --git a/lib/util/http/bitbucket.ts b/lib/util/http/bitbucket.ts index de4c406dffd675..138c31aff6d79d 100644 --- a/lib/util/http/bitbucket.ts +++ b/lib/util/http/bitbucket.ts @@ -2,7 +2,7 @@ import is from '@sindresorhus/is'; import { logger } from '../../logger'; import type { PagedResult } from '../../modules/platform/bitbucket/types'; import { parseUrl, resolveBaseUrl } from '../url'; -import type { HttpOptions, HttpRequestOptions, HttpResponse } from './types'; +import type { HttpOptions, HttpResponse } from './types'; import { Http } from '.'; const MAX_PAGES = 100; @@ -26,7 +26,7 @@ export class BitbucketHttp extends Http { protected override async request( path: string, - options?: BitbucketHttpOptions & HttpRequestOptions, + options?: BitbucketHttpOptions, ): Promise> { const opts = { baseUrl, ...options }; diff --git a/lib/util/http/cache/abstract-http-cache-provider.ts b/lib/util/http/cache/abstract-http-cache-provider.ts new file mode 100644 index 00000000000000..938e60b836dca6 --- /dev/null +++ b/lib/util/http/cache/abstract-http-cache-provider.ts @@ -0,0 +1,94 @@ +import { logger } from '../../../logger'; +import { HttpCacheStats } from '../../stats'; +import type { GotOptions, HttpResponse } from '../types'; +import { copyResponse } from '../util'; +import { HttpCacheSchema } from './schema'; +import type { HttpCache, HttpCacheProvider } from './types'; + +export abstract class AbstractHttpCacheProvider implements HttpCacheProvider { + protected abstract load(url: string): Promise; + protected abstract persist(url: string, data: HttpCache): Promise; + + async get(url: string): Promise { + const cache = await this.load(url); + const httpCache = HttpCacheSchema.parse(cache); + if (!httpCache) { + return null; + } + + return httpCache as HttpCache; + } + + async setCacheHeaders>( + url: string, + opts: T, + ): Promise { + const httpCache = await this.get(url); + if (!httpCache) { + return; + } + + opts.headers ??= {}; + + if (httpCache.etag) { + opts.headers['If-None-Match'] = httpCache.etag; + } + + if (httpCache.lastModified) { + opts.headers['If-Modified-Since'] = httpCache.lastModified; + } + } + + async wrapResponse( + url: string, + resp: HttpResponse, + ): Promise> { + if (resp.statusCode === 200) { + const etag = resp.headers?.['etag']; + const lastModified = resp.headers?.['last-modified']; + + HttpCacheStats.incRemoteMisses(url); + + const httpResponse = copyResponse(resp, true); + const timestamp = new Date().toISOString(); + + const newHttpCache = HttpCacheSchema.parse({ + etag, + lastModified, + httpResponse, + timestamp, + }); + if (newHttpCache) { + logger.debug( + `http cache: saving ${url} (etag=${etag}, lastModified=${lastModified})`, + ); + await this.persist(url, newHttpCache as HttpCache); + } else { + logger.debug(`http cache: failed to persist cache for ${url}`); + } + + return resp; + } + + if (resp.statusCode === 304) { + const httpCache = await this.get(url); + if (!httpCache) { + return resp; + } + + const timestamp = httpCache.timestamp; + logger.debug( + `http cache: Using cached response: ${url} from ${timestamp}`, + ); + HttpCacheStats.incRemoteHits(url); + const cachedResp = copyResponse( + httpCache.httpResponse as HttpResponse, + true, + ); + cachedResp.authorization = resp.authorization; + return cachedResp; + } + + return resp; + } +} diff --git a/lib/util/http/cache/repository-http-cache-provider.spec.ts b/lib/util/http/cache/repository-http-cache-provider.spec.ts new file mode 100644 index 00000000000000..a8acbba4b11786 --- /dev/null +++ b/lib/util/http/cache/repository-http-cache-provider.spec.ts @@ -0,0 +1,152 @@ +import { Http } from '..'; +import * as httpMock from '../../../../test/http-mock'; +import { logger } from '../../../../test/util'; +import { getCache, resetCache } from '../../cache/repository'; +import { repoCacheProvider } from './repository-http-cache-provider'; + +describe('util/http/cache/repository-http-cache-provider', () => { + beforeEach(() => { + resetCache(); + }); + + const http = new Http('test', { + cacheProvider: repoCacheProvider, + }); + + it('reuses data with etag', async () => { + const scope = httpMock.scope('https://example.com'); + + scope.get('/foo/bar').reply(200, { msg: 'Hello, world!' }, { etag: '123' }); + const res1 = await http.getJson('https://example.com/foo/bar'); + expect(res1).toMatchObject({ + statusCode: 200, + body: { msg: 'Hello, world!' }, + authorization: false, + }); + + scope.get('/foo/bar').reply(304); + const res2 = await http.getJson('https://example.com/foo/bar'); + expect(res2).toMatchObject({ + statusCode: 200, + body: { msg: 'Hello, world!' }, + authorization: false, + }); + }); + + it('reuses data with last-modified', async () => { + const scope = httpMock.scope('https://example.com'); + + scope + .get('/foo/bar') + .reply( + 200, + { msg: 'Hello, world!' }, + { 'last-modified': 'Mon, 01 Jan 2000 00:00:00 GMT' }, + ); + const res1 = await http.getJson('https://example.com/foo/bar'); + expect(res1).toMatchObject({ + statusCode: 200, + body: { msg: 'Hello, world!' }, + authorization: false, + }); + + scope.get('/foo/bar').reply(304); + const res2 = await http.getJson('https://example.com/foo/bar'); + expect(res2).toMatchObject({ + statusCode: 200, + body: { msg: 'Hello, world!' }, + authorization: false, + }); + }); + + it('uses older cache format', async () => { + const repoCache = getCache(); + repoCache.httpCache = { + 'https://example.com/foo/bar': { + etag: '123', + lastModified: 'Mon, 01 Jan 2000 00:00:00 GMT', + httpResponse: { statusCode: 200, body: { msg: 'Hello, world!' } }, + timeStamp: new Date().toISOString(), + }, + }; + httpMock.scope('https://example.com').get('/foo/bar').reply(304); + + const res = await http.getJson('https://example.com/foo/bar'); + + expect(res).toMatchObject({ + statusCode: 200, + body: { msg: 'Hello, world!' }, + authorization: false, + }); + }); + + it('reports if cache could not be persisted', async () => { + httpMock + .scope('https://example.com') + .get('/foo/bar') + .reply(200, { msg: 'Hello, world!' }); + + await http.getJson('https://example.com/foo/bar'); + + expect(logger.logger.debug).toHaveBeenCalledWith( + 'http cache: failed to persist cache for https://example.com/foo/bar', + ); + }); + + it('handles abrupt cache reset', async () => { + const scope = httpMock.scope('https://example.com'); + + scope.get('/foo/bar').reply(200, { msg: 'Hello, world!' }, { etag: '123' }); + const res1 = await http.getJson('https://example.com/foo/bar'); + expect(res1).toMatchObject({ + statusCode: 200, + body: { msg: 'Hello, world!' }, + authorization: false, + }); + + resetCache(); + + scope.get('/foo/bar').reply(304); + const res2 = await http.getJson('https://example.com/foo/bar'); + expect(res2).toMatchObject({ + statusCode: 304, + authorization: false, + }); + }); + + it('bypasses for statuses other than 200 and 304', async () => { + const scope = httpMock.scope('https://example.com'); + scope.get('/foo/bar').reply(203); + + const res = await http.getJson('https://example.com/foo/bar'); + + expect(res).toMatchObject({ + statusCode: 203, + authorization: false, + }); + }); + + it('supports authorization', async () => { + const scope = httpMock.scope('https://example.com'); + + scope.get('/foo/bar').reply(200, { msg: 'Hello, world!' }, { etag: '123' }); + const res1 = await http.getJson('https://example.com/foo/bar', { + headers: { authorization: 'Bearer 123' }, + }); + expect(res1).toMatchObject({ + statusCode: 200, + body: { msg: 'Hello, world!' }, + authorization: true, + }); + + scope.get('/foo/bar').reply(304); + const res2 = await http.getJson('https://example.com/foo/bar', { + headers: { authorization: 'Bearer 123' }, + }); + expect(res2).toMatchObject({ + statusCode: 200, + body: { msg: 'Hello, world!' }, + authorization: true, + }); + }); +}); diff --git a/lib/util/http/cache/repository-http-cache-provider.ts b/lib/util/http/cache/repository-http-cache-provider.ts new file mode 100644 index 00000000000000..9cf9c8cfa3b0c1 --- /dev/null +++ b/lib/util/http/cache/repository-http-cache-provider.ts @@ -0,0 +1,20 @@ +import { getCache } from '../../cache/repository'; +import { AbstractHttpCacheProvider } from './abstract-http-cache-provider'; +import type { HttpCache } from './types'; + +export class RepositoryHttpCacheProvider extends AbstractHttpCacheProvider { + override load(url: string): Promise { + const cache = getCache(); + cache.httpCache ??= {}; + return Promise.resolve(cache.httpCache[url]); + } + + override persist(url: string, data: HttpCache): Promise { + const cache = getCache(); + cache.httpCache ??= {}; + cache.httpCache[url] = data; + return Promise.resolve(); + } +} + +export const repoCacheProvider = new RepositoryHttpCacheProvider(); diff --git a/lib/util/http/cache/schema.ts b/lib/util/http/cache/schema.ts new file mode 100644 index 00000000000000..d1d71fda9bc61a --- /dev/null +++ b/lib/util/http/cache/schema.ts @@ -0,0 +1,34 @@ +import { z } from 'zod'; + +const invalidFieldsMsg = + 'Cache object should have `etag` or `lastModified` fields'; + +export const HttpCacheSchema = z + .object({ + // TODO: remove this migration part during the Christmas eve 2024 + timeStamp: z.string().optional(), + timestamp: z.string().optional(), + }) + .passthrough() + .transform((data) => { + if (data.timeStamp) { + data.timestamp = data.timeStamp; + delete data.timeStamp; + } + return data; + }) + .pipe( + z + .object({ + etag: z.string().optional(), + lastModified: z.string().optional(), + httpResponse: z.unknown(), + timestamp: z.string(), + }) + .refine( + ({ etag, lastModified }) => etag ?? lastModified, + invalidFieldsMsg, + ), + ) + .nullable() + .catch(null); diff --git a/lib/util/http/cache/types.ts b/lib/util/http/cache/types.ts new file mode 100644 index 00000000000000..1159f58028762f --- /dev/null +++ b/lib/util/http/cache/types.ts @@ -0,0 +1,17 @@ +import type { GotOptions, HttpResponse } from '../types'; + +export interface HttpCache { + etag?: string; + lastModified?: string; + httpResponse: unknown; + timestamp: string; +} + +export interface HttpCacheProvider { + setCacheHeaders>( + url: string, + opts: T, + ): Promise; + + wrapResponse(url: string, resp: HttpResponse): Promise>; +} diff --git a/lib/util/http/gerrit.ts b/lib/util/http/gerrit.ts index a163da021fc5ee..a690ba951b3147 100644 --- a/lib/util/http/gerrit.ts +++ b/lib/util/http/gerrit.ts @@ -1,6 +1,6 @@ import { parseJson } from '../common'; import { regEx } from '../regex'; -import { validateUrl } from '../url'; +import { isHttpUrl } from '../url'; import type { HttpOptions, HttpResponse, InternalHttpOptions } from './types'; import { Http } from './index'; @@ -24,7 +24,7 @@ export class GerritHttp extends Http { path: string, options?: InternalHttpOptions, ): Promise> { - const url = validateUrl(path) ? path : baseUrl + path; + const url = isHttpUrl(path) ? path : baseUrl + path; const opts: InternalHttpOptions = { parseJson: (text: string) => parseJson(text.replace(GerritHttp.magicPrefix, ''), path), diff --git a/lib/util/http/gitea.ts b/lib/util/http/gitea.ts index c8296939e45f80..f69470c44ed6f3 100644 --- a/lib/util/http/gitea.ts +++ b/lib/util/http/gitea.ts @@ -1,11 +1,6 @@ import is from '@sindresorhus/is'; import { resolveBaseUrl } from '../url'; -import type { - HttpOptions, - HttpRequestOptions, - HttpResponse, - InternalHttpOptions, -} from './types'; +import type { HttpOptions, HttpResponse, InternalHttpOptions } from './types'; import { Http } from '.'; let baseUrl: string; @@ -41,7 +36,7 @@ export class GiteaHttp extends Http { protected override async request( path: string, - options?: InternalHttpOptions & GiteaHttpOptions & HttpRequestOptions, + options?: InternalHttpOptions & GiteaHttpOptions, ): Promise> { const resolvedUrl = resolveUrl(path, options?.baseUrl ?? baseUrl); const opts = { diff --git a/lib/util/http/github.ts b/lib/util/http/github.ts index 65f9e6e81ba22b..3db9d7b4a741dc 100644 --- a/lib/util/http/github.ts +++ b/lib/util/http/github.ts @@ -19,7 +19,6 @@ import type { GotLegacyError } from './legacy'; import type { GraphqlOptions, HttpOptions, - HttpRequestOptions, HttpResponse, InternalHttpOptions, } from './types'; @@ -274,10 +273,10 @@ export class GithubHttp extends Http { protected override async request( url: string | URL, - options?: InternalHttpOptions & GithubHttpOptions & HttpRequestOptions, + options?: InternalHttpOptions & GithubHttpOptions, okToRetry = true, ): Promise> { - const opts: GithubHttpOptions = { + const opts: InternalHttpOptions & GithubHttpOptions = { baseUrl, ...options, throwHttpErrors: true, @@ -297,8 +296,17 @@ export class GithubHttp extends Http { ); } + let readOnly = opts.readOnly; + const { method = 'get' } = opts; + if ( + readOnly === undefined && + ['get', 'head'].includes(method.toLowerCase()) + ) { + readOnly = true; + } const { token } = findMatchingRule(authUrl.toString(), { hostType: this.hostType, + readOnly, }); opts.token = token; } @@ -340,7 +348,7 @@ export class GithubHttp extends Http { nextUrl.searchParams.set('page', String(pageNumber)); return this.request( nextUrl, - { ...opts, paginate: false, repoCache: false }, + { ...opts, paginate: false, cacheProvider: undefined }, okToRetry, ); }, @@ -394,6 +402,7 @@ export class GithubHttp extends Http { baseUrl: baseUrl.replace('/v3/', '/'), // GHE uses unversioned graphql path body, headers: { accept: options?.acceptHeader }, + readOnly: options.readOnly, }; if (options.token) { opts.token = options.token; diff --git a/lib/util/http/gitlab.spec.ts b/lib/util/http/gitlab.spec.ts index ff3bcb5077c790..f3f3363eb64fa6 100644 --- a/lib/util/http/gitlab.spec.ts +++ b/lib/util/http/gitlab.spec.ts @@ -1,3 +1,4 @@ +import { HTTPError } from 'got'; import * as httpMock from '../../../test/http-mock'; import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages'; import { GitlabReleasesDatasource } from '../../modules/datasource/gitlab-releases'; @@ -144,4 +145,44 @@ describe('util/http/gitlab', () => { ); }); }); + + describe('handles 409 errors', () => { + let NODE_ENV: string | undefined; + + beforeAll(() => { + // Unset NODE_ENV so that we can test the retry logic + NODE_ENV = process.env.NODE_ENV; + delete process.env.NODE_ENV; + }); + + afterAll(() => { + process.env.NODE_ENV = NODE_ENV; + }); + + it('retries the request on resource lock', async () => { + const body = { message: '409 Conflict: Resource lock' }; + httpMock.scope(gitlabApiHost).post('/api/v4/some-url').reply(409, body); + httpMock.scope(gitlabApiHost).post('/api/v4/some-url').reply(200, {}); + const res = await gitlabApi.postJson('some-url', {}); + expect(res.statusCode).toBe(200); + }); + + it('does not retry more than twice on resource lock', async () => { + const body = { message: '409 Conflict: Resource lock' }; + httpMock.scope(gitlabApiHost).post('/api/v4/some-url').reply(409, body); + httpMock.scope(gitlabApiHost).post('/api/v4/some-url').reply(409, body); + httpMock.scope(gitlabApiHost).post('/api/v4/some-url').reply(409, body); + await expect(gitlabApi.postJson('some-url', {})).rejects.toThrow( + HTTPError, + ); + }); + + it('does not retry for other reasons', async () => { + const body = { message: 'Other reason' }; + httpMock.scope(gitlabApiHost).post('/api/v4/some-url').reply(409, body); + await expect(gitlabApi.postJson('some-url', {})).rejects.toThrow( + HTTPError, + ); + }); + }); }); diff --git a/lib/util/http/gitlab.ts b/lib/util/http/gitlab.ts index 770e9d1cf4a49d..28beca26261f11 100644 --- a/lib/util/http/gitlab.ts +++ b/lib/util/http/gitlab.ts @@ -1,13 +1,9 @@ import is from '@sindresorhus/is'; +import type { RetryObject } from 'got'; import { logger } from '../../logger'; import { ExternalHostError } from '../../types/errors/external-host-error'; import { parseLinkHeader, parseUrl } from '../url'; -import type { - HttpOptions, - HttpRequestOptions, - HttpResponse, - InternalHttpOptions, -} from './types'; +import type { HttpOptions, HttpResponse, InternalHttpOptions } from './types'; import { Http } from '.'; let baseUrl = 'https://gitlab.com/api/v4/'; @@ -26,7 +22,7 @@ export class GitlabHttp extends Http { protected override async request( url: string | URL, - options?: InternalHttpOptions & GitlabHttpOptions & HttpRequestOptions, + options?: InternalHttpOptions & GitlabHttpOptions, ): Promise> { const opts = { baseUrl, @@ -88,4 +84,19 @@ export class GitlabHttp extends Http { throw err; } } + + protected override calculateRetryDelay(retryObject: RetryObject): number { + const { error, attemptCount, retryOptions } = retryObject; + if ( + attemptCount <= retryOptions.limit && + error.options.method === 'POST' && + error.response?.statusCode === 409 && + error.response.rawBody.toString().includes('Resource lock') + ) { + const noise = Math.random() * 100; + return 2 ** (attemptCount - 1) * 1000 + noise; + } + + return super.calculateRetryDelay(retryObject); + } } diff --git a/lib/util/http/host-rules.ts b/lib/util/http/host-rules.ts index 20c898103743a2..09775fe9f412fe 100644 --- a/lib/util/http/host-rules.ts +++ b/lib/util/http/host-rules.ts @@ -14,10 +14,10 @@ import { matchRegexOrGlobList } from '../string-match'; import { parseUrl } from '../url'; import { dnsLookup } from './dns'; import { keepAliveAgents } from './keep-alive'; -import type { GotOptions } from './types'; +import type { GotOptions, InternalHttpOptions } from './types'; export type HostRulesGotOptions = Pick< - GotOptions, + GotOptions & InternalHttpOptions, | 'hostType' | 'url' | 'noAuth' @@ -34,14 +34,15 @@ export type HostRulesGotOptions = Pick< | 'agent' | 'http2' | 'https' + | 'readOnly' >; export function findMatchingRule( url: string, options: GotOptions, ): HostRule { - const { hostType } = options; - let res = hostRules.find({ hostType, url }); + const { hostType, readOnly } = options; + let res = hostRules.find({ hostType, url, readOnly }); if ( is.nonEmptyString(res.token) || diff --git a/lib/util/http/index.spec.ts b/lib/util/http/index.spec.ts index 4541f6b86a2af9..3c1b37bb755f65 100644 --- a/lib/util/http/index.spec.ts +++ b/lib/util/http/index.spec.ts @@ -6,6 +6,7 @@ import { HOST_DISABLED, } from '../../constants/error-messages'; import * as memCache from '../cache/memory'; +import { resetCache } from '../cache/repository'; import * as hostRules from '../host-rules'; import * as queue from './queue'; import * as throttle from './throttle'; @@ -22,6 +23,7 @@ describe('util/http/index', () => { hostRules.clear(); queue.clear(); throttle.clear(); + resetCache(); }); it('get', async () => { @@ -77,84 +79,16 @@ describe('util/http/index', () => { }) .get('/') .reply(200, '{ "test": true }', { etag: 'abc123' }); - expect( - await http.getJson('http://renovate.com', { repoCache: true }), - ).toEqual({ - authorization: false, - body: { - test: true, - }, - headers: { - etag: 'abc123', - }, - statusCode: 200, - }); - httpMock - .scope(baseUrl, { - reqheaders: { - accept: 'application/json', - }, - }) - .get('/') - .reply(304, '', { etag: 'abc123' }); - expect( - await http.getJson('http://renovate.com', { repoCache: true }), - ).toEqual({ - authorization: false, - body: { - test: true, - }, - headers: { - etag: 'abc123', - }, - statusCode: 200, - }); - }); - - it('uses last-modified header for caching', async () => { - httpMock - .scope(baseUrl, { - reqheaders: { - accept: 'application/json', - }, - }) - .get('/') - .reply(200, '{ "test": true }', { - 'last-modified': 'Sun, 18 Feb 2024 18:00:05 GMT', - }); - expect( - await http.getJson('http://renovate.com', { repoCache: true }), - ).toEqual({ - authorization: false, - body: { - test: true, - }, - headers: { - 'last-modified': 'Sun, 18 Feb 2024 18:00:05 GMT', - }, - statusCode: 200, - }); + const res = await http.getJson('http://renovate.com'); - httpMock - .scope(baseUrl, { - reqheaders: { - accept: 'application/json', - }, - }) - .get('/') - .reply(304, '', { - 'last-modified': 'Sun, 18 Feb 2024 18:00:05 GMT', - }); - expect( - await http.getJson('http://renovate.com', { repoCache: true }), - ).toEqual({ + expect(res).toEqual({ authorization: false, body: { test: true, }, headers: { - 'last-modified': 'Sun, 18 Feb 2024 18:00:05 GMT', + etag: 'abc123', }, statusCode: 200, }); @@ -546,78 +480,4 @@ describe('util/http/index', () => { expect(t2 - t1).toBeGreaterThanOrEqual(4000); }); }); - - describe('Etag caching', () => { - it('returns cached data for status=304', async () => { - type FooBar = { foo: string; bar: string }; - const data: FooBar = { foo: 'foo', bar: 'bar' }; - httpMock - .scope(baseUrl, { reqheaders: { 'If-None-Match': 'foobar' } }) - .get('/foo') - .reply(304); - - const res = await http.getJson(`/foo`, { - baseUrl, - etagCache: { - etag: 'foobar', - data, - }, - }); - - expect(res.statusCode).toBe(304); - expect(res.body).toEqual(data); - expect(res.body).not.toBe(data); - }); - - it('bypasses schema parsing', async () => { - const FooBar = z - .object({ foo: z.string(), bar: z.string() }) - .transform(({ foo, bar }) => ({ - foobar: `${foo}${bar}`.toUpperCase(), - })); - const data = FooBar.parse({ foo: 'foo', bar: 'bar' }); - httpMock - .scope(baseUrl, { reqheaders: { 'If-None-Match': 'foobar' } }) - .get('/foo') - .reply(304); - - const res = await http.getJson( - `/foo`, - { - baseUrl, - etagCache: { - etag: 'foobar', - data, - }, - }, - FooBar, - ); - - expect(res.statusCode).toBe(304); - expect(res.body).toEqual(data); - expect(res.body).not.toBe(data); - }); - - it('returns new data for status=200', async () => { - type FooBar = { foo: string; bar: string }; - const oldData: FooBar = { foo: 'foo', bar: 'bar' }; - const newData: FooBar = { foo: 'FOO', bar: 'BAR' }; - httpMock - .scope(baseUrl, { reqheaders: { 'If-None-Match': 'foobar' } }) - .get('/foo') - .reply(200, newData); - - const res = await http.getJson(`/foo`, { - baseUrl, - etagCache: { - etag: 'foobar', - data: oldData, - }, - }); - - expect(res.statusCode).toBe(200); - expect(res.body).toEqual(newData); - expect(res.body).not.toBe(newData); - }); - }); }); diff --git a/lib/util/http/index.ts b/lib/util/http/index.ts index c58279d4210ccc..0a9f2e313b9f21 100644 --- a/lib/util/http/index.ts +++ b/lib/util/http/index.ts @@ -1,16 +1,17 @@ +import is from '@sindresorhus/is'; import merge from 'deepmerge'; -import got, { Options, RequestError } from 'got'; +import got, { Options, RequestError, RetryObject } from 'got'; import type { SetRequired } from 'type-fest'; import { infer as Infer, type ZodError, ZodType } from 'zod'; +import { GlobalConfig } from '../../config/global'; import { HOST_DISABLED } from '../../constants/error-messages'; import { pkg } from '../../expose.cjs'; import { logger } from '../../logger'; import { ExternalHostError } from '../../types/errors/external-host-error'; import * as memCache from '../cache/memory'; -import { getCache } from '../cache/repository'; -import { clone } from '../clone'; import { hash } from '../hash'; import { type AsyncResult, Result } from '../result'; +import { type HttpRequestStatsDataPoint, HttpStats } from '../stats'; import { resolveBaseUrl } from '../url'; import { applyAuthorization, removeAuthorization } from './auth'; import { hooks } from './hooks'; @@ -23,13 +24,12 @@ import type { GotOptions, GotTask, HttpOptions, - HttpRequestOptions, HttpResponse, InternalHttpOptions, - RequestStats, } from './types'; // TODO: refactor code to remove this (#9651) import './legacy'; +import { copyResponse } from './util'; export { RequestError as HttpError }; @@ -37,7 +37,7 @@ export class EmptyResultError extends Error {} export type SafeJsonError = RequestError | ZodError | EmptyResultError; type JsonArgs< - Opts extends HttpOptions & HttpRequestOptions, + Opts extends HttpOptions, ResT = unknown, Schema extends ZodType = ZodType, > = { @@ -46,36 +46,18 @@ type JsonArgs< schema?: Schema; }; -// Copying will help to avoid circular structure -// and mutation of the cached response. -function copyResponse( - response: HttpResponse, - deep: boolean, -): HttpResponse { - const { body, statusCode, headers } = response; - return deep - ? { - statusCode, - body: body instanceof Buffer ? (body.subarray() as T) : clone(body), - headers: clone(headers), - } - : { - statusCode, - body, - headers, - }; -} - function applyDefaultHeaders(options: Options): void { const renovateVersion = pkg.version; options.headers = { ...options.headers, 'user-agent': - process.env.RENOVATE_USER_AGENT ?? + GlobalConfig.get('userAgent') ?? `RenovateBot/${renovateVersion} (https://github.com/renovatebot/renovate)`, }; } +type QueueStatsData = Pick; + // Note on types: // options.requestType can be either 'json' or 'buffer', but `T` should be // `Buffer` in the latter case. @@ -84,7 +66,7 @@ function applyDefaultHeaders(options: Options): void { async function gotTask( url: string, options: SetRequired, - requestStats: Omit, + queueStats: QueueStatsData, ): Promise> { logger.trace({ url, options }, 'got request'); @@ -119,9 +101,13 @@ async function gotTask( throw error; } finally { - const httpRequests = memCache.get('http-requests') || []; - httpRequests.push({ ...requestStats, duration, statusCode }); - memCache.set('http-requests', httpRequests); + HttpStats.write({ + method: options.method, + url, + reqMs: duration, + queueMs: queueStats.queueMs, + status: statusCode, + }); } } @@ -133,13 +119,19 @@ export class Http { options: HttpOptions = {}, ) { const retryLimit = process.env.NODE_ENV === 'test' ? 0 : 2; - this.options = merge(options, { - context: { hostType }, - retry: { - limit: retryLimit, - maxRetryAfter: 0, // Don't rely on `got` retry-after handling, just let it fail and then we'll handle it + this.options = merge( + options, + { + context: { hostType }, + retry: { + calculateDelay: (retryObject) => + this.calculateRetryDelay(retryObject), + limit: retryLimit, + maxRetryAfter: 0, // Don't rely on `got` retry-after handling, just let it fail and then we'll handle it + }, }, - }); + { isMergeableObject: is.plainObject }, + ); } protected getThrottle(url: string): Throttle | null { @@ -148,41 +140,38 @@ export class Http { protected async request( requestUrl: string | URL, - httpOptions: InternalHttpOptions & HttpRequestOptions, + httpOptions: InternalHttpOptions, ): Promise> { let url = requestUrl.toString(); if (httpOptions?.baseUrl) { url = resolveBaseUrl(httpOptions.baseUrl, url); } - let options = merge, GotOptions>( + let options = merge, InternalHttpOptions>( { method: 'get', ...this.options, hostType: this.hostType, }, httpOptions, + { isMergeableObject: is.plainObject }, ); logger.trace(`HTTP request: ${options.method.toUpperCase()} ${url}`); - const etagCache = - httpOptions.etagCache && options.method === 'get' - ? httpOptions.etagCache - : null; - if (etagCache) { - options.headers = { - ...options.headers, - 'If-None-Match': etagCache.etag, - }; - } - options.hooks = { beforeRedirect: [removeAuthorization], }; applyDefaultHeaders(options); + if ( + is.undefined(options.readOnly) && + ['head', 'get'].includes(options.method) + ) { + options.readOnly = true; + } + const hostRule = findMatchingRule(url, options); options = applyHostRule(url, options, hostRule); if (options.enabled === false) { @@ -213,35 +202,14 @@ export class Http { // istanbul ignore else: no cache tests if (!resPromise) { - if (httpOptions.repoCache) { - const responseCache = getCache().httpCache?.[url]; - // Prefer If-Modified-Since over If-None-Match - if (responseCache?.['lastModified']) { - logger.debug( - `http cache: trying cached Last-Modified "${responseCache?.['lastModified']}" for ${url}`, - ); - options.headers = { - ...options.headers, - 'If-Modified-Since': responseCache['lastModified'], - }; - } else if (responseCache?.etag) { - logger.debug( - `http cache: trying cached etag "${responseCache.etag}" for ${url}`, - ); - options.headers = { - ...options.headers, - 'If-None-Match': responseCache.etag, - }; - } + if (options.cacheProvider) { + await options.cacheProvider.setCacheHeaders(url, options); } + const startTime = Date.now(); const httpTask: GotTask = () => { - const queueDuration = Date.now() - startTime; - return gotTask(url, options, { - method: options.method, - url, - queueDuration, - }); + const queueMs = Date.now() - startTime; + return gotTask(url, options, { queueMs }); }; const throttle = this.getThrottle(url); @@ -267,35 +235,11 @@ export class Http { const deepCopyNeeded = !!memCacheKey && res.statusCode !== 304; const resCopy = copyResponse(res, deepCopyNeeded); resCopy.authorization = !!options?.headers?.authorization; - if (httpOptions.repoCache) { - const cache = getCache(); - cache.httpCache ??= {}; - if ( - resCopy.statusCode === 200 && - (resCopy.headers?.etag ?? resCopy.headers['last-modified']) - ) { - logger.debug( - `http cache: saving ${url} (etag=${resCopy.headers.etag}, lastModified=${resCopy.headers['last-modified']})`, - ); - cache.httpCache[url] = { - etag: resCopy.headers.etag, - httpResponse: copyResponse(res, deepCopyNeeded), - lastModified: resCopy.headers['last-modified'], - timeStamp: new Date().toISOString(), - }; - } - if (resCopy.statusCode === 304 && cache.httpCache[url]?.httpResponse) { - logger.debug( - `http cache: Using cached response: ${url} from ${cache.httpCache[url].timeStamp}`, - ); - const cacheCopy = copyResponse( - cache.httpCache[url].httpResponse, - deepCopyNeeded, - ); - cacheCopy.authorization = !!options?.headers?.authorization; - return cacheCopy as HttpResponse; - } + + if (options.cacheProvider) { + return await options.cacheProvider.wrapResponse(url, resCopy); } + return resCopy; } catch (err) { const { abortOnError, abortIgnoreStatusCodes } = options; @@ -306,10 +250,11 @@ export class Http { } } - get( - url: string, - options: HttpOptions & HttpRequestOptions = {}, - ): Promise { + protected calculateRetryDelay({ computedValue }: RetryObject): number { + return computedValue; + } + + get(url: string, options: HttpOptions = {}): Promise { return this.request(url, options); } @@ -329,11 +274,7 @@ export class Http { private async requestJson( method: InternalHttpOptions['method'], - { - url, - httpOptions: requestOptions, - schema, - }: JsonArgs, ResT>, + { url, httpOptions: requestOptions, schema }: JsonArgs, ): Promise> { const { body, ...httpOptions } = { ...requestOptions }; const opts: InternalHttpOptions = { @@ -351,23 +292,11 @@ export class Http { } const res = await this.request(url, opts); - const etagCacheHit = - httpOptions.etagCache && res.statusCode === 304 - ? clone(httpOptions.etagCache.data) - : null; - if (!schema) { - if (etagCacheHit) { - res.body = etagCacheHit; - } return res; } - if (etagCacheHit) { - res.body = etagCacheHit; - } else { - res.body = await schema.parseAsync(res.body); - } + res.body = await schema.parseAsync(res.body); return res; } @@ -401,22 +330,19 @@ export class Http { }); } - getJson( - url: string, - options?: Opts & HttpRequestOptions, - ): Promise>; + getJson(url: string, options?: Opts): Promise>; getJson = ZodType>( url: string, schema: Schema, ): Promise>>; getJson = ZodType>( url: string, - options: Opts & HttpRequestOptions>, + options: Opts, schema: Schema, ): Promise>>; getJson = ZodType>( arg1: string, - arg2?: (Opts & HttpRequestOptions) | Schema, + arg2?: Opts | Schema, arg3?: Schema, ): Promise> { const args = this.resolveArgs(arg1, arg2, arg3); @@ -432,7 +358,7 @@ export class Http { Schema extends ZodType = ZodType, >( url: string, - options: Opts & HttpRequestOptions>, + options: Opts, schema: Schema, ): AsyncResult, SafeJsonError>; getJsonSafe< @@ -440,7 +366,7 @@ export class Http { Schema extends ZodType = ZodType, >( arg1: string, - arg2?: (Opts & HttpRequestOptions) | Schema, + arg2?: Opts | Schema, arg3?: Schema, ): AsyncResult { const args = this.resolveArgs(arg1, arg2, arg3); @@ -545,6 +471,14 @@ export class Http { } applyDefaultHeaders(combinedOptions); + + if ( + is.undefined(combinedOptions.readOnly) && + ['head', 'get'].includes(combinedOptions.method) + ) { + combinedOptions.readOnly = true; + } + const hostRule = findMatchingRule(url, combinedOptions); combinedOptions = applyHostRule(resolvedUrl, combinedOptions, hostRule); if (combinedOptions.enabled === false) { diff --git a/lib/util/http/jira.ts b/lib/util/http/jira.ts index 7463d7b6ebb1a7..706780b284915e 100644 --- a/lib/util/http/jira.ts +++ b/lib/util/http/jira.ts @@ -1,9 +1,4 @@ -import type { - HttpOptions, - HttpRequestOptions, - HttpResponse, - InternalHttpOptions, -} from './types'; +import type { HttpOptions, HttpResponse, InternalHttpOptions } from './types'; import { Http } from '.'; let baseUrl: string; @@ -19,7 +14,7 @@ export class JiraHttp extends Http { protected override request( url: string | URL, - options?: InternalHttpOptions & HttpRequestOptions, + options?: InternalHttpOptions, ): Promise> { const opts = { baseUrl, ...options }; return super.request(url, opts); diff --git a/lib/util/http/types.ts b/lib/util/http/types.ts index 5969aeade45806..a767c29c5b7c6b 100644 --- a/lib/util/http/types.ts +++ b/lib/util/http/types.ts @@ -4,6 +4,7 @@ import type { OptionsOfJSONResponseBody, ParseJsonFunction, } from 'got'; +import type { HttpCacheProvider } from './cache/types'; export type GotContextOptions = { authType?: string; @@ -47,6 +48,7 @@ export interface GraphqlOptions { cursor?: string | null; acceptHeader?: string; token?: string; + readOnly?: boolean; } export interface HttpOptions { @@ -65,16 +67,8 @@ export interface HttpOptions { token?: string; memCache?: boolean; - repoCache?: boolean; -} - -export interface EtagCache { - etag: string; - data: T; -} - -export interface HttpRequestOptions { - etagCache?: EtagCache; + cacheProvider?: HttpCacheProvider; + readOnly?: boolean; } export interface InternalHttpOptions extends HttpOptions { @@ -82,7 +76,6 @@ export interface InternalHttpOptions extends HttpOptions { responseType?: 'json' | 'buffer'; method?: 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head'; parseJson?: ParseJsonFunction; - repoCache?: boolean; } export interface HttpHeaders extends IncomingHttpHeaders { diff --git a/lib/util/http/util.ts b/lib/util/http/util.ts new file mode 100644 index 00000000000000..aa0a045c27ff85 --- /dev/null +++ b/lib/util/http/util.ts @@ -0,0 +1,22 @@ +import { clone } from '../clone'; +import type { HttpResponse } from './types'; + +// Copying will help to avoid circular structure +// and mutation of the cached response. +export function copyResponse( + response: HttpResponse, + deep: boolean, +): HttpResponse { + const { body, statusCode, headers } = response; + return deep + ? { + statusCode, + body: body instanceof Buffer ? (body.subarray() as T) : clone(body), + headers: clone(headers), + } + : { + statusCode, + body, + headers, + }; +} diff --git a/lib/util/merge-confidence/index.spec.ts b/lib/util/merge-confidence/index.spec.ts index 9b672ba0ef78af..9529f061e43ebf 100644 --- a/lib/util/merge-confidence/index.spec.ts +++ b/lib/util/merge-confidence/index.spec.ts @@ -1,4 +1,5 @@ import * as httpMock from '../../../test/http-mock'; +import { GlobalConfig } from '../../config/global'; import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages'; import { logger } from '../../logger'; import type { HostRule } from '../../types'; @@ -17,6 +18,15 @@ import { describe('util/merge-confidence/index', () => { const apiBaseUrl = 'https://www.baseurl.com/'; const defaultApiBaseUrl = 'https://developer.mend.io/'; + const supportedDatasources = [ + 'go', + 'maven', + 'npm', + 'nuget', + 'packagist', + 'pypi', + 'rubygems', + ]; describe('isActiveConfidenceLevel()', () => { it('returns false if null', () => { @@ -57,10 +67,10 @@ describe('util/merge-confidence/index', () => { }; beforeEach(() => { - process.env.RENOVATE_X_MERGE_CONFIDENCE_API_BASE_URL = apiBaseUrl; hostRules.add(hostRule); initConfig(); memCache.reset(); + GlobalConfig.set({ mergeConfidenceEndpoint: apiBaseUrl }); }); afterEach(() => { @@ -302,16 +312,13 @@ describe('util/merge-confidence/index', () => { }); it('using default base url if none is set', async () => { - delete process.env.RENOVATE_X_MERGE_CONFIDENCE_API_BASE_URL; + GlobalConfig.reset(); httpMock .scope(defaultApiBaseUrl) .get(`/api/mc/availability`) .reply(200); await expect(initMergeConfidence()).toResolve(); - expect(logger.trace).toHaveBeenCalledWith( - 'using default merge confidence API base URL', - ); expect(logger.debug).toHaveBeenCalledWith( { supportedDatasources: [ @@ -329,8 +336,7 @@ describe('util/merge-confidence/index', () => { }); it('warns and then resolves if base url is invalid', async () => { - process.env.RENOVATE_X_MERGE_CONFIDENCE_API_BASE_URL = - 'invalid-url.com'; + GlobalConfig.set({ mergeConfidenceEndpoint: 'invalid-url.com' }); httpMock .scope(defaultApiBaseUrl) .get(`/api/mc/availability`) @@ -349,7 +355,7 @@ describe('util/merge-confidence/index', () => { it('uses a custom base url containing path', async () => { const renovateApi = 'https://domain.com/proxy/renovate-api'; - process.env.RENOVATE_X_MERGE_CONFIDENCE_API_BASE_URL = renovateApi; + GlobalConfig.set({ mergeConfidenceEndpoint: renovateApi }); httpMock.scope(renovateApi).get(`/api/mc/availability`).reply(200); await expect(initMergeConfidence()).toResolve(); @@ -423,54 +429,42 @@ describe('util/merge-confidence/index', () => { }); describe('parseSupportedDatasourceList()', () => { - type ParseSupportedDatasourceTestCase = { - name: string; - datasourceListString: string | undefined; - expected: string[] | undefined; - }; - afterEach(() => { - delete process.env.RENOVATE_X_MERGE_CONFIDENCE_SUPPORTED_DATASOURCES; + GlobalConfig.reset(); }); it.each([ { - name: 'it should do nothing when the input is undefined', - datasourceListString: undefined, - expected: undefined, + name: 'it should do return default value when the input is undefined', + datasources: undefined, + expected: supportedDatasources, }, { name: 'it should successfully parse the given datasource list', - datasourceListString: `["go","npm"]`, + datasources: ['go', 'npm'], expected: ['go', 'npm'], }, { - name: 'it should gracefully handle invalid json', - datasourceListString: `{`, - expected: undefined, + name: 'it should gracefully handle invalid JSON', + datasources: `{`, + expected: supportedDatasources, }, { name: 'it should discard non-array JSON input', - datasourceListString: `{}`, - expected: undefined, + datasources: `{}`, + expected: supportedDatasources, }, { name: 'it should discard non-string array JSON input', - datasourceListString: `[1,2]`, - expected: undefined, + datasources: `[1,2]`, + expected: supportedDatasources, }, - ])( - `$name`, - ({ - datasourceListString, - expected, - }: ParseSupportedDatasourceTestCase) => { - process.env.RENOVATE_X_MERGE_CONFIDENCE_SUPPORTED_DATASOURCES = - datasourceListString; - - expect(parseSupportedDatasourceString()).toStrictEqual(expected); - }, - ); + ])(`$name`, ({ datasources, expected }) => { + GlobalConfig.set({ + mergeConfidenceDatasources: datasources, + }); + expect(parseSupportedDatasourceString()).toStrictEqual(expected); + }); }); }); }); diff --git a/lib/util/merge-confidence/index.ts b/lib/util/merge-confidence/index.ts index 56f2f177a52870..05c29c8676b223 100644 --- a/lib/util/merge-confidence/index.ts +++ b/lib/util/merge-confidence/index.ts @@ -1,12 +1,13 @@ import is from '@sindresorhus/is'; +import { GlobalConfig } from '../../config/global'; import { supportedDatasources as presetSupportedDatasources } from '../../config/presets/internal/merge-confidence'; import type { UpdateType } from '../../config/types'; import { logger } from '../../logger'; import { ExternalHostError } from '../../types/errors/external-host-error'; import * as packageCache from '../cache/package'; -import { parseJson } from '../common'; import * as hostRules from '../host-rules'; import { Http } from '../http'; +import { regEx } from '../regex'; import { ensureTrailingSlash, joinUrlParts } from '../url'; import { MERGE_CONFIDENCE } from './common'; import type { MergeConfidence } from './types'; @@ -27,41 +28,28 @@ export const confidenceLevels: Record = { export function initConfig(): void { apiBaseUrl = getApiBaseUrl(); token = getApiToken(); - supportedDatasources = - parseSupportedDatasourceString() ?? presetSupportedDatasources; + supportedDatasources = parseSupportedDatasourceString(); if (!is.nullOrUndefined(token)) { logger.debug(`Merge confidence token found for ${apiBaseUrl}`); } } -export function parseSupportedDatasourceString(): string[] | undefined { - const supportedDatasourceString = - process.env.RENOVATE_X_MERGE_CONFIDENCE_SUPPORTED_DATASOURCES; - - if (!is.string(supportedDatasourceString)) { - return undefined; - } - - let parsedDatasourceList: unknown; - try { - parsedDatasourceList = parseJson(supportedDatasourceString, '.json5'); - } catch (err) { - logger.error( - { supportedDatasourceString, err }, - 'Failed to parse supported datasources list; Invalid JSON format', - ); - } +export function parseSupportedDatasourceString(): string[] { + const supportedDatasources = GlobalConfig.get( + 'mergeConfidenceDatasources', + presetSupportedDatasources, + ); - if (!is.array(parsedDatasourceList, is.string)) { + if (!is.array(supportedDatasources, is.string)) { logger.warn( - { parsedDatasourceList }, - `Expected a string array but got ${typeof parsedDatasourceList}`, + { supportedDatasources }, + `Expected a string array but got ${typeof supportedDatasources} - using default value instead`, ); - return undefined; + return presetSupportedDatasources; } - return parsedDatasourceList; + return supportedDatasources; } export function resetConfig(): void { @@ -164,7 +152,7 @@ async function queryApi( return 'neutral'; } - const escapedPackageName = packageName.replace('/', '%2f'); + const escapedPackageName = packageName.replace(regEx(/\//g), '%2f'); const url = joinUrlParts( apiBaseUrl, 'api/mc/json', @@ -241,12 +229,10 @@ export async function initMergeConfidence(): Promise { function getApiBaseUrl(): string { const defaultBaseUrl = 'https://developer.mend.io/'; - const baseFromEnv = process.env.RENOVATE_X_MERGE_CONFIDENCE_API_BASE_URL; - - if (is.nullOrUndefined(baseFromEnv)) { - logger.trace('using default merge confidence API base URL'); - return defaultBaseUrl; - } + const baseFromEnv = GlobalConfig.get( + 'mergeConfidenceEndpoint', + defaultBaseUrl, + ); try { const parsedBaseUrl = new URL(baseFromEnv).toString(); diff --git a/lib/util/package-rules/current-value.spec.ts b/lib/util/package-rules/current-value.spec.ts index 55e2f8bd66ad9c..dfa955f2b32d2c 100644 --- a/lib/util/package-rules/current-value.spec.ts +++ b/lib/util/package-rules/current-value.spec.ts @@ -4,13 +4,37 @@ describe('util/package-rules/current-value', () => { const matcher = new CurrentValueMatcher(); describe('match', () => { - it('return null if non-regex', () => { + it('return true for exact match', () => { const result = matcher.matches( { - currentValue: '"~> 1.1.0"', + currentValue: '1.1.0', + }, + { + matchCurrentValue: '1.1.0', + }, + ); + expect(result).toBeTrue(); + }); + + it('return true for glob match', () => { + const result = matcher.matches( + { + currentValue: '1.2.3', + }, + { + matchCurrentValue: '1.2.*', + }, + ); + expect(result).toBeTrue(); + }); + + it('return false for glob non match', () => { + const result = matcher.matches( + { + currentValue: '1.2.3', }, { - matchCurrentValue: '^v', + matchCurrentValue: '1.3.*', }, ); expect(result).toBeFalse(); diff --git a/lib/util/package-rules/current-value.ts b/lib/util/package-rules/current-value.ts index 8d322b93439c72..fe19e75c304315 100644 --- a/lib/util/package-rules/current-value.ts +++ b/lib/util/package-rules/current-value.ts @@ -1,7 +1,6 @@ import is from '@sindresorhus/is'; import type { PackageRule, PackageRuleInputConfig } from '../../config/types'; -import { logger } from '../../logger'; -import { getRegexPredicate } from '../string-match'; +import { getRegexOrGlobPredicate } from '../string-match'; import { Matcher } from './base'; export class CurrentValueMatcher extends Matcher { @@ -12,15 +11,7 @@ export class CurrentValueMatcher extends Matcher { if (is.undefined(matchCurrentValue)) { return null; } - const matchCurrentValuePred = getRegexPredicate(matchCurrentValue); - - if (!matchCurrentValuePred) { - logger.debug( - { matchCurrentValue }, - 'matchCurrentValue should be a regex, starting and ending with `/`', - ); - return false; - } + const matchCurrentValuePred = getRegexOrGlobPredicate(matchCurrentValue); if (!currentValue) { return false; diff --git a/lib/util/package-rules/current-version.spec.ts b/lib/util/package-rules/current-version.spec.ts index d8b8ee034df1a4..07a4ba1dfc9229 100644 --- a/lib/util/package-rules/current-version.spec.ts +++ b/lib/util/package-rules/current-version.spec.ts @@ -20,7 +20,7 @@ describe('util/package-rules/current-version', () => { }); it('return false on version exception', () => { - const spy = jest.spyOn(pep440, 'matches').mockImplementationOnce(() => { + const spy = jest.spyOn(pep440, 'isValid').mockImplementationOnce(() => { throw new Error(); }); const result = matcher.matches( @@ -36,6 +36,19 @@ describe('util/package-rules/current-version', () => { expect(spy.mock.calls).toHaveLength(1); }); + it('return true for a valid match', () => { + const result = matcher.matches( + { + versioning: 'pep440', + currentValue: '1.2.3', + }, + { + matchCurrentVersion: '<1.2.3.5', + }, + ); + expect(result).toBeTrue(); + }); + it('return false if no version could be found', () => { const result = matcher.matches( { diff --git a/lib/util/package-rules/dep-prefixes.spec.ts b/lib/util/package-rules/dep-prefixes.spec.ts new file mode 100644 index 00000000000000..d5b35da075989d --- /dev/null +++ b/lib/util/package-rules/dep-prefixes.spec.ts @@ -0,0 +1,105 @@ +import { DepPrefixesMatcher } from './dep-prefixes'; + +describe('util/package-rules/dep-prefixes', () => { + const depPrefixesMatcher = new DepPrefixesMatcher(); + + describe('match', () => { + it('should return null if matchDepPrefixes is not defined', () => { + const result = depPrefixesMatcher.matches( + { + depName: 'abc1', + }, + { + matchDepPrefixes: undefined, + }, + ); + expect(result).toBeNull(); + }); + + it('should return false if depName is not defined', () => { + const result = depPrefixesMatcher.matches( + { + depName: undefined, + }, + { + matchDepPrefixes: ['@opentelemetry'], + }, + ); + expect(result).toBeFalse(); + }); + + it('should return true if depName matched', () => { + const result = depPrefixesMatcher.matches( + { + depName: 'abc1', + }, + { + matchDepPrefixes: ['abc'], + }, + ); + expect(result).toBeTrue(); + }); + + it('should return false if depName does not match', () => { + const result = depPrefixesMatcher.matches( + { + depName: 'abc1', + }, + { + matchDepPrefixes: ['def'], + }, + ); + expect(result).toBeFalse(); + }); + }); + + describe('exclude', () => { + it('should return null if excludeDepPrefixes is not defined', () => { + const result = depPrefixesMatcher.excludes( + { + depName: 'abc1', + }, + { + excludeDepPrefixes: undefined, + }, + ); + expect(result).toBeNull(); + }); + + it('should return false if depName is not defined', () => { + const result = depPrefixesMatcher.excludes( + { + depName: undefined, + }, + { + excludeDepPrefixes: ['@opentelemetry'], + }, + ); + expect(result).toBeFalse(); + }); + + it('should return true if depName matched', () => { + const result = depPrefixesMatcher.excludes( + { + depName: 'abc1', + }, + { + excludeDepPrefixes: ['abc'], + }, + ); + expect(result).toBeTrue(); + }); + + it('should return false if depName does not match', () => { + const result = depPrefixesMatcher.excludes( + { + depName: 'abc1', + }, + { + excludeDepPrefixes: ['def'], + }, + ); + expect(result).toBeFalse(); + }); + }); +}); diff --git a/lib/util/package-rules/dep-prefixes.ts b/lib/util/package-rules/dep-prefixes.ts new file mode 100644 index 00000000000000..351df41f8605e5 --- /dev/null +++ b/lib/util/package-rules/dep-prefixes.ts @@ -0,0 +1,35 @@ +import is from '@sindresorhus/is'; +import type { PackageRule, PackageRuleInputConfig } from '../../config/types'; +import { Matcher } from './base'; + +export class DepPrefixesMatcher extends Matcher { + override matches( + { depName }: PackageRuleInputConfig, + { matchDepPrefixes }: PackageRule, + ): boolean | null { + if (is.undefined(matchDepPrefixes)) { + return null; + } + + if (is.undefined(depName)) { + return false; + } + + return matchDepPrefixes.some((prefix) => depName.startsWith(prefix)); + } + + override excludes( + { depName }: PackageRuleInputConfig, + { excludeDepPrefixes }: PackageRule, + ): boolean | null { + if (is.undefined(excludeDepPrefixes)) { + return null; + } + + if (is.undefined(depName)) { + return false; + } + + return excludeDepPrefixes.some((prefix) => depName.startsWith(prefix)); + } +} diff --git a/lib/util/package-rules/index.spec.ts b/lib/util/package-rules/index.spec.ts index 80e5716ee7a9ce..b47ca9e99b877f 100644 --- a/lib/util/package-rules/index.spec.ts +++ b/lib/util/package-rules/index.spec.ts @@ -210,6 +210,34 @@ describe('util/package-rules/index', () => { expect(res2.automerge).toBeFalse(); }); + it('sets skipReason=package-rules if enabled=false', () => { + const dep: any = { + depName: 'foo', + packageRules: [ + { + enabled: false, + }, + ], + }; + const res = applyPackageRules(dep); + expect(res.enabled).toBeFalse(); + expect(res.skipReason).toBe('package-rules'); + }); + + it('skips skipReason=package-rules if enabled=true', () => { + const dep: any = { + enabled: false, + depName: 'foo', + packageRules: [ + { + enabled: false, + }, + ], + }; + const res = applyPackageRules(dep); + expect(res.skipReason).toBeUndefined(); + }); + it('matches anything if missing inclusive rules', () => { const config: TestConfig = { packageRules: [ @@ -1230,4 +1258,52 @@ describe('util/package-rules/index', () => { expect(res1.x).toBeUndefined(); expect(res2.x).toBe(1); }); + + it('matches matchDepPrefixes(depName)', () => { + const config: TestConfig = { + packageRules: [ + { + matchDepPrefixes: ['abc'], + x: 1, + }, + ], + }; + + const res1 = applyPackageRules({ + ...config, + depName: 'abc1', + }); + const res2 = applyPackageRules({ + ...config, + depName: 'def1', + }); + applyPackageRules(config); // coverage + + expect(res1.x).toBe(1); + expect(res2.x).toBeUndefined(); + }); + + it('matches excludeDepPrefixes(depName)', () => { + const config: TestConfig = { + packageRules: [ + { + excludeDepPrefixes: ['abc'], + x: 1, + }, + ], + }; + + const res1 = applyPackageRules({ + ...config, + depName: 'abc1', + }); + const res2 = applyPackageRules({ + ...config, + depName: 'def1', + }); + applyPackageRules(config); // coverage + + expect(res1.x).toBeUndefined(); + expect(res2.x).toBe(1); + }); }); diff --git a/lib/util/package-rules/index.ts b/lib/util/package-rules/index.ts index 9fee74a23bd821..f625ea5b24462f 100644 --- a/lib/util/package-rules/index.ts +++ b/lib/util/package-rules/index.ts @@ -80,6 +80,9 @@ export function applyPackageRules( lower: true, }); } + if (toApply.enabled === false && config.enabled !== false) { + config.skipReason = 'package-rules'; + } config = mergeChildConfig(config, toApply); } } diff --git a/lib/util/package-rules/matchers.ts b/lib/util/package-rules/matchers.ts index 4bef5c5784eb5e..80a0d19ff7c171 100644 --- a/lib/util/package-rules/matchers.ts +++ b/lib/util/package-rules/matchers.ts @@ -6,6 +6,7 @@ import { CurrentVersionMatcher } from './current-version'; import { DatasourcesMatcher } from './datasources'; import { DepNameMatcher } from './dep-names'; import { DepPatternsMatcher } from './dep-patterns'; +import { DepPrefixesMatcher } from './dep-prefixes'; import { DepTypesMatcher } from './dep-types'; import { FileNamesMatcher } from './files'; import { ManagersMatcher } from './managers'; @@ -32,6 +33,7 @@ matchers.push([new MergeConfidenceMatcher()]); matchers.push([ new DepNameMatcher(), new DepPatternsMatcher(), + new DepPrefixesMatcher(), new PackageNameMatcher(), new PackagePatternsMatcher(), new PackagePrefixesMatcher(), diff --git a/lib/util/package-rules/new-value.spec.ts b/lib/util/package-rules/new-value.spec.ts index 323de64d54021c..1868a61ebf9676 100644 --- a/lib/util/package-rules/new-value.spec.ts +++ b/lib/util/package-rules/new-value.spec.ts @@ -4,13 +4,37 @@ describe('util/package-rules/new-value', () => { const matcher = new NewValueMatcher(); describe('match', () => { - it('return null if non-regex', () => { + it('return true for exact match', () => { const result = matcher.matches( { - newValue: '"~> 1.1.0"', + newValue: '1.1.0', + }, + { + matchNewValue: '1.1.0', + }, + ); + expect(result).toBeTrue(); + }); + + it('return true for glob match', () => { + const result = matcher.matches( + { + newValue: '1.2.3', + }, + { + matchNewValue: '1.2.*', + }, + ); + expect(result).toBeTrue(); + }); + + it('return false for glob non match', () => { + const result = matcher.matches( + { + newValue: '1.2.3', }, { - matchNewValue: '^v', + matchNewValue: '1.3.*', }, ); expect(result).toBeFalse(); diff --git a/lib/util/package-rules/new-value.ts b/lib/util/package-rules/new-value.ts index b8c28f5f82193e..de15ad3d1d089f 100644 --- a/lib/util/package-rules/new-value.ts +++ b/lib/util/package-rules/new-value.ts @@ -1,7 +1,6 @@ import is from '@sindresorhus/is'; import type { PackageRule, PackageRuleInputConfig } from '../../config/types'; -import { logger } from '../../logger'; -import { getRegexPredicate } from '../string-match'; +import { getRegexOrGlobPredicate } from '../string-match'; import { Matcher } from './base'; export class NewValueMatcher extends Matcher { @@ -12,15 +11,7 @@ export class NewValueMatcher extends Matcher { if (is.undefined(matchNewValue)) { return null; } - const matchNewValuePred = getRegexPredicate(matchNewValue); - - if (!matchNewValuePred) { - logger.debug( - { matchNewValue }, - 'matchNewValue should be a regex, starting and ending with `/`', - ); - return false; - } + const matchNewValuePred = getRegexOrGlobPredicate(matchNewValue); if (!newValue) { return false; diff --git a/lib/util/package-rules/package-names.spec.ts b/lib/util/package-rules/package-names.spec.ts index f33b00c7e117d2..522f0ea0c3cc2c 100644 --- a/lib/util/package-rules/package-names.spec.ts +++ b/lib/util/package-rules/package-names.spec.ts @@ -1,3 +1,4 @@ +import { logger } from '../../../test/util'; import { PackageNameMatcher } from './package-names'; describe('util/package-rules/package-names', () => { @@ -16,6 +17,19 @@ describe('util/package-rules/package-names', () => { expect(result).toBeFalse(); }); + it('should return false if not matching', () => { + const result = packageNameMatcher.matches( + { + depName: 'abc', + packageName: 'def', + }, + { + matchPackageNames: ['ghi'], + }, + ); + expect(result).toBeFalse(); + }); + it('should matchPackageName', () => { const result = packageNameMatcher.matches( { @@ -23,7 +37,7 @@ describe('util/package-rules/package-names', () => { packageName: 'def', }, { - matchPackageNames: ['def'], + matchPackageNames: ['def', 'ghi'], }, ); expect(result).toBeTrue(); @@ -36,15 +50,16 @@ describe('util/package-rules/package-names', () => { packageName: 'def', }, { - matchPackageNames: ['abc'], + matchPackageNames: ['ghi', 'abc'], }, ); expect(result).toBeTrue(); + expect(logger.logger.once.warn).toHaveBeenCalled(); }); }); describe('exclude', () => { - it('should return false if packageFile is not defined', () => { + it('should return false if packageName is not defined', () => { const result = packageNameMatcher.excludes( { depName: undefined, @@ -55,18 +70,45 @@ describe('util/package-rules/package-names', () => { ); expect(result).toBeFalse(); }); - }); - it('should excludePackageName', () => { - const result = packageNameMatcher.excludes( - { - depName: 'abc', - packageName: 'def', - }, - { - excludePackageNames: ['def'], - }, - ); - expect(result).toBeTrue(); + it('should return false if not matching', () => { + const result = packageNameMatcher.excludes( + { + depName: 'abc', + packageName: 'def', + }, + { + excludePackageNames: ['ghi'], + }, + ); + expect(result).toBeFalse(); + }); + + it('should excludePackageName', () => { + const result = packageNameMatcher.excludes( + { + depName: 'abc', + packageName: 'def', + }, + { + excludePackageNames: ['def', 'ghi'], + }, + ); + expect(result).toBeTrue(); + }); + + it('should fall back to depName excludePackageName', () => { + const result = packageNameMatcher.excludes( + { + depName: 'abc', + packageName: 'def', + }, + { + excludePackageNames: ['abc', 'ghi'], + }, + ); + expect(result).toBeTrue(); + expect(logger.logger.once.warn).toHaveBeenCalled(); + }); }); }); diff --git a/lib/util/package-rules/package-names.ts b/lib/util/package-rules/package-names.ts index 7af49f4beb979b..ee8cc6befb14c7 100644 --- a/lib/util/package-rules/package-names.ts +++ b/lib/util/package-rules/package-names.ts @@ -21,7 +21,7 @@ export class PackageNameMatcher extends Matcher { } if (matchPackageNames.includes(depName)) { - logger.once.info( + logger.once.warn( { packageRule, packageName, depName }, 'Use matchDepNames instead of matchPackageNames', ); @@ -48,7 +48,7 @@ export class PackageNameMatcher extends Matcher { } if (excludePackageNames.includes(depName)) { - logger.once.info( + logger.once.warn( { packageRule, packageName, depName }, 'Use excludeDepNames instead of excludePackageNames', ); diff --git a/lib/util/package-rules/package-patterns.spec.ts b/lib/util/package-rules/package-patterns.spec.ts index fcb614a2d21858..e6b1665510be3a 100644 --- a/lib/util/package-rules/package-patterns.spec.ts +++ b/lib/util/package-rules/package-patterns.spec.ts @@ -1,11 +1,11 @@ import { PackagePatternsMatcher } from './package-patterns'; describe('util/package-rules/package-patterns', () => { - const packageNameMatcher = new PackagePatternsMatcher(); + const packagePatternsMatcher = new PackagePatternsMatcher(); describe('match', () => { it('should return false if depName is not defined', () => { - const result = packageNameMatcher.matches( + const result = packagePatternsMatcher.matches( { depName: undefined, }, @@ -17,7 +17,7 @@ describe('util/package-rules/package-patterns', () => { }); it('should match packageName', () => { - const result = packageNameMatcher.matches( + const result = packagePatternsMatcher.matches( { depName: 'abc', packageName: 'def', @@ -30,7 +30,7 @@ describe('util/package-rules/package-patterns', () => { }); it('should fall back to matching depName', () => { - const result = packageNameMatcher.matches( + const result = packagePatternsMatcher.matches( { depName: 'abc', packageName: 'def', @@ -45,7 +45,7 @@ describe('util/package-rules/package-patterns', () => { describe('exclude', () => { it('should exclude packageName', () => { - const result = packageNameMatcher.excludes( + const result = packagePatternsMatcher.excludes( { depName: 'abc', packageName: 'def', diff --git a/lib/util/package-rules/package-patterns.ts b/lib/util/package-rules/package-patterns.ts index bc4f4029ed788b..9fc28c7509f821 100644 --- a/lib/util/package-rules/package-patterns.ts +++ b/lib/util/package-rules/package-patterns.ts @@ -39,7 +39,7 @@ export class PackagePatternsMatcher extends Matcher { return true; } if (matchPatternsAgainstName(matchPackagePatterns, depName)) { - logger.once.info( + logger.once.warn( { packageRule, packageName, depName }, 'Use matchDepPatterns instead of matchPackagePatterns', ); @@ -70,7 +70,7 @@ export class PackagePatternsMatcher extends Matcher { } if (matchPatternsAgainstName(excludePackagePatterns, depName)) { - logger.once.info( + logger.once.warn( { packageRule, packageName, depName }, 'Use excludeDepPatterns instead of excludePackagePatterns', ); diff --git a/lib/util/package-rules/package-prefixes.ts b/lib/util/package-rules/package-prefixes.ts index bb9b015b2ecaf4..a5502654cb3072 100644 --- a/lib/util/package-rules/package-prefixes.ts +++ b/lib/util/package-rules/package-prefixes.ts @@ -23,7 +23,7 @@ export class PackagePrefixesMatcher extends Matcher { return true; } if (matchPackagePrefixes.some((prefix) => depName.startsWith(prefix))) { - logger.once.info( + logger.once.warn( { packageName, depName }, 'Use matchDepPatterns instead of matchPackagePrefixes', ); @@ -52,7 +52,7 @@ export class PackagePrefixesMatcher extends Matcher { return true; } if (excludePackagePrefixes.some((prefix) => depName.startsWith(prefix))) { - logger.once.info( + logger.once.warn( { packageName, depName }, 'Use excludeDepPatterns instead of excludePackagePrefixes', ); diff --git a/lib/util/package-rules/repositories.spec.ts b/lib/util/package-rules/repositories.spec.ts index 86de8b656a0fa5..369927de30b157 100644 --- a/lib/util/package-rules/repositories.spec.ts +++ b/lib/util/package-rules/repositories.spec.ts @@ -1,11 +1,11 @@ import { RepositoriesMatcher } from './repositories'; describe('util/package-rules/repositories', () => { - const packageNameMatcher = new RepositoriesMatcher(); + const repositoryMatcher = new RepositoriesMatcher(); describe('match', () => { it('should return null if match repositories is not defined', () => { - const result = packageNameMatcher.matches( + const result = repositoryMatcher.matches( { repository: 'org/repo', }, @@ -17,7 +17,7 @@ describe('util/package-rules/repositories', () => { }); it('should return false if repository is not defined', () => { - const result = packageNameMatcher.matches( + const result = repositoryMatcher.matches( { repository: undefined, }, @@ -29,7 +29,7 @@ describe('util/package-rules/repositories', () => { }); it('should return true if repository matches regex pattern', () => { - const result = packageNameMatcher.matches( + const result = repositoryMatcher.matches( { repository: 'org/repo', }, @@ -41,7 +41,7 @@ describe('util/package-rules/repositories', () => { }); it('should return false if repository has invalid regex pattern', () => { - const result = packageNameMatcher.matches( + const result = repositoryMatcher.matches( { repository: 'org/repo', }, @@ -53,7 +53,7 @@ describe('util/package-rules/repositories', () => { }); it('should return false if repository does not match regex pattern', () => { - const result = packageNameMatcher.matches( + const result = repositoryMatcher.matches( { repository: 'org/repo', }, @@ -65,7 +65,7 @@ describe('util/package-rules/repositories', () => { }); it('should return true if repository matches minimatch pattern', () => { - const result = packageNameMatcher.matches( + const result = repositoryMatcher.matches( { repository: 'org/repo', }, @@ -77,7 +77,7 @@ describe('util/package-rules/repositories', () => { }); it('should return false if repository does not match minimatch pattern', () => { - const result = packageNameMatcher.matches( + const result = repositoryMatcher.matches( { repository: 'org/repo', }, @@ -89,7 +89,7 @@ describe('util/package-rules/repositories', () => { }); it('should return true if repository matches at least one pattern', () => { - const result = packageNameMatcher.matches( + const result = repositoryMatcher.matches( { repository: 'org/repo-archived', }, @@ -103,7 +103,7 @@ describe('util/package-rules/repositories', () => { describe('excludes', () => { it('should return null if exclude repositories is not defined', () => { - const result = packageNameMatcher.excludes( + const result = repositoryMatcher.excludes( { repository: 'org/repo', }, @@ -115,7 +115,7 @@ describe('util/package-rules/repositories', () => { }); it('should return false if exclude repository is not defined', () => { - const result = packageNameMatcher.excludes( + const result = repositoryMatcher.excludes( { repository: undefined, }, @@ -127,7 +127,7 @@ describe('util/package-rules/repositories', () => { }); it('should return true if exclude repository matches regex pattern', () => { - const result = packageNameMatcher.excludes( + const result = repositoryMatcher.excludes( { repository: 'org/repo', }, @@ -139,7 +139,7 @@ describe('util/package-rules/repositories', () => { }); it('should return false if exclude repository has invalid regex pattern', () => { - const result = packageNameMatcher.excludes( + const result = repositoryMatcher.excludes( { repository: 'org/repo', }, @@ -151,7 +151,7 @@ describe('util/package-rules/repositories', () => { }); it('should return false if exclude repository does not match regex pattern', () => { - const result = packageNameMatcher.excludes( + const result = repositoryMatcher.excludes( { repository: 'org/repo', }, @@ -163,7 +163,7 @@ describe('util/package-rules/repositories', () => { }); it('should return true if exclude repository matches minimatch pattern', () => { - const result = packageNameMatcher.excludes( + const result = repositoryMatcher.excludes( { repository: 'org/repo', }, @@ -175,7 +175,7 @@ describe('util/package-rules/repositories', () => { }); it('should return false if exclude repository does not match minimatch pattern', () => { - const result = packageNameMatcher.excludes( + const result = repositoryMatcher.excludes( { repository: 'org/repo', }, @@ -187,7 +187,7 @@ describe('util/package-rules/repositories', () => { }); it('should return true if exclude repository matches at least one pattern', () => { - const result = packageNameMatcher.excludes( + const result = repositoryMatcher.excludes( { repository: 'org/repo-archived', }, diff --git a/lib/util/schema-utils.spec.ts b/lib/util/schema-utils.spec.ts index 390c2ed2bb0832..ccca2f70b9e8e1 100644 --- a/lib/util/schema-utils.spec.ts +++ b/lib/util/schema-utils.spec.ts @@ -3,6 +3,7 @@ import { z } from 'zod'; import { Json, Json5, + Jsonc, LooseArray, LooseRecord, MultidocYaml, @@ -269,6 +270,72 @@ describe('util/schema-utils', () => { }); }); + describe('Jsonc', () => { + it('parses JSONC', () => { + const Schema = Jsonc.pipe(z.object({ foo: z.literal('bar') })); + + expect(Schema.parse('{"foo": "bar"}')).toEqual({ foo: 'bar' }); + + expect(Schema.safeParse(42)).toMatchObject({ + error: { + issues: [ + { + message: 'Expected string, received number', + code: 'invalid_type', + expected: 'string', + received: 'number', + path: [], + }, + ], + }, + success: false, + }); + + expect(Schema.safeParse('{"foo": "foo"}')).toMatchObject({ + error: { + issues: [ + { + message: 'Invalid literal value, expected "bar"', + code: 'invalid_literal', + expected: 'bar', + received: 'foo', + path: ['foo'], + }, + ], + }, + success: false, + }); + + expect(Schema.safeParse('["foo", "bar"]')).toMatchObject({ + error: { + issues: [ + { + message: 'Expected object, received array', + code: 'invalid_type', + expected: 'object', + received: 'array', + path: [], + }, + ], + }, + success: false, + }); + + expect(Schema.safeParse('{')).toMatchObject({ + error: { + issues: [ + { + message: 'Invalid JSONC', + code: 'custom', + path: [], + }, + ], + }, + success: false, + }); + }); + }); + describe('UtcDate', () => { it('parses date', () => { expect(UtcDate.parse('2020-04-04').toString()).toBe( diff --git a/lib/util/schema-utils.ts b/lib/util/schema-utils.ts index f4ad31e0315080..1f7cb3d6da15ad 100644 --- a/lib/util/schema-utils.ts +++ b/lib/util/schema-utils.ts @@ -1,4 +1,5 @@ import JSON5 from 'json5'; +import * as JSONC from 'jsonc-parser'; import { DateTime } from 'luxon'; import type { JsonArray, JsonValue } from 'type-fest'; import { z } from 'zod'; @@ -215,6 +216,16 @@ export const Json5 = z.string().transform((str, ctx): JsonValue => { } }); +export const Jsonc = z.string().transform((str, ctx): JsonValue => { + const errors: JSONC.ParseError[] = []; + const value = JSONC.parse(str, errors); + if (errors.length === 0) { + return value; + } + ctx.addIssue({ code: 'custom', message: 'Invalid JSONC' }); + return z.NEVER; +}); + export const UtcDate = z .string({ description: 'ISO 8601 string' }) .transform((str, ctx): DateTime => { @@ -228,7 +239,7 @@ export const UtcDate = z export const Yaml = z.string().transform((str, ctx): JsonValue => { try { - return parseSingleYaml(str, { json: true }) as JsonValue; + return parseSingleYaml(str, { json: true }); } catch (e) { ctx.addIssue({ code: 'custom', message: 'Invalid YAML' }); return z.NEVER; diff --git a/lib/util/stats.spec.ts b/lib/util/stats.spec.ts new file mode 100644 index 00000000000000..1d7ceb3ba951e4 --- /dev/null +++ b/lib/util/stats.spec.ts @@ -0,0 +1,517 @@ +import { logger } from '../../test/util'; +import * as memCache from './cache/memory'; +import { + HttpCacheStats, + HttpStats, + LookupStats, + PackageCacheStats, + makeTimingReport, +} from './stats'; + +describe('util/stats', () => { + beforeEach(() => { + memCache.init(); + }); + + describe('makeTimingReport', () => { + it('supports empty data', () => { + const res = makeTimingReport([]); + expect(res).toEqual({ + avgMs: 0, + count: 0, + maxMs: 0, + medianMs: 0, + totalMs: 0, + }); + }); + + it('supports single data point', () => { + const res = makeTimingReport([100]); + expect(res).toEqual({ + avgMs: 100, + count: 1, + maxMs: 100, + medianMs: 100, + totalMs: 100, + }); + }); + + it('supports multiple data points', () => { + const res = makeTimingReport([100, 200, 400]); + expect(res).toEqual({ + avgMs: 233, + count: 3, + maxMs: 400, + medianMs: 200, + totalMs: 700, + }); + }); + }); + + describe('LookupStats', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('returns empty report', () => { + const res = LookupStats.getReport(); + expect(res).toEqual({}); + }); + + it('writes data points', () => { + LookupStats.write('npm', 100); + LookupStats.write('npm', 200); + LookupStats.write('npm', 400); + LookupStats.write('docker', 1000); + + const res = LookupStats.getReport(); + + expect(res).toEqual({ + docker: { + avgMs: 1000, + count: 1, + maxMs: 1000, + medianMs: 1000, + totalMs: 1000, + }, + npm: { + avgMs: 233, + count: 3, + maxMs: 400, + medianMs: 200, + totalMs: 700, + }, + }); + }); + + it('wraps a function', async () => { + const res = await LookupStats.wrap('npm', () => { + jest.advanceTimersByTime(100); + return Promise.resolve('foo'); + }); + + expect(res).toBe('foo'); + expect(LookupStats.getReport()).toEqual({ + npm: { + avgMs: 100, + count: 1, + maxMs: 100, + medianMs: 100, + totalMs: 100, + }, + }); + }); + + it('logs report', () => { + LookupStats.write('npm', 100); + LookupStats.write('npm', 200); + LookupStats.write('npm', 400); + LookupStats.write('docker', 1000); + + LookupStats.report(); + + expect(logger.logger.debug).toHaveBeenCalledTimes(1); + const [data, msg] = logger.logger.debug.mock.calls[0]; + expect(msg).toBe('Lookup statistics'); + expect(data).toEqual({ + docker: { + avgMs: 1000, + count: 1, + maxMs: 1000, + medianMs: 1000, + totalMs: 1000, + }, + npm: { + avgMs: 233, + count: 3, + maxMs: 400, + medianMs: 200, + totalMs: 700, + }, + }); + }); + }); + + describe('PackageCacheStats', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + it('returns empty report', () => { + const res = PackageCacheStats.getReport(); + expect(res).toEqual({ + get: { avgMs: 0, count: 0, maxMs: 0, medianMs: 0, totalMs: 0 }, + set: { avgMs: 0, count: 0, maxMs: 0, medianMs: 0, totalMs: 0 }, + }); + }); + + it('writes data points', () => { + PackageCacheStats.writeGet(100); + PackageCacheStats.writeGet(200); + PackageCacheStats.writeGet(400); + PackageCacheStats.writeSet(1000); + + const res = PackageCacheStats.getReport(); + + expect(res).toEqual({ + get: { + avgMs: 233, + count: 3, + maxMs: 400, + medianMs: 200, + totalMs: 700, + }, + set: { + avgMs: 1000, + count: 1, + maxMs: 1000, + medianMs: 1000, + totalMs: 1000, + }, + }); + }); + + it('wraps get function', async () => { + const res = await PackageCacheStats.wrapGet(() => { + jest.advanceTimersByTime(100); + return Promise.resolve('foo'); + }); + + expect(res).toBe('foo'); + expect(PackageCacheStats.getReport()).toEqual({ + get: { avgMs: 100, count: 1, maxMs: 100, medianMs: 100, totalMs: 100 }, + set: { avgMs: 0, count: 0, maxMs: 0, medianMs: 0, totalMs: 0 }, + }); + }); + + it('wraps set function', async () => { + await PackageCacheStats.wrapSet(() => { + jest.advanceTimersByTime(100); + return Promise.resolve(); + }); + + expect(PackageCacheStats.getReport()).toEqual({ + get: { avgMs: 0, count: 0, maxMs: 0, medianMs: 0, totalMs: 0 }, + set: { avgMs: 100, count: 1, maxMs: 100, medianMs: 100, totalMs: 100 }, + }); + }); + + it('logs report', () => { + PackageCacheStats.writeGet(100); + PackageCacheStats.writeGet(200); + PackageCacheStats.writeGet(400); + PackageCacheStats.writeSet(1000); + + PackageCacheStats.report(); + + expect(logger.logger.debug).toHaveBeenCalledTimes(1); + const [data, msg] = logger.logger.debug.mock.calls[0]; + expect(msg).toBe('Package cache statistics'); + expect(data).toEqual({ + get: { + avgMs: 233, + count: 3, + maxMs: 400, + medianMs: 200, + totalMs: 700, + }, + set: { + avgMs: 1000, + count: 1, + maxMs: 1000, + medianMs: 1000, + totalMs: 1000, + }, + }); + }); + }); + + describe('HttpStats', () => { + it('returns empty report', () => { + const res = HttpStats.getReport(); + expect(res).toEqual({ + hostRequests: {}, + hosts: {}, + rawRequests: [], + requests: 0, + urls: {}, + }); + }); + + it('writes data points', () => { + HttpStats.write({ + method: 'GET', + url: 'https://example.com/foo', + reqMs: 100, + queueMs: 10, + status: 200, + }); + HttpStats.write({ + method: 'GET', + url: 'https://example.com/foo', + reqMs: 200, + queueMs: 20, + status: 200, + }); + HttpStats.write({ + method: 'GET', + url: 'https://example.com/bar', + reqMs: 400, + queueMs: 40, + status: 200, + }); + HttpStats.write({ + method: 'GET', + url: 'https://example.com/foo', + reqMs: 800, + queueMs: 80, + status: 404, + }); + HttpStats.write({ + method: 'GET', + url: '', + reqMs: 100, + queueMs: 100, + status: 400, + }); + + const res = HttpStats.getReport(); + + expect(res).toEqual({ + hostRequests: { + 'example.com': [ + { + method: 'GET', + queueMs: 40, + reqMs: 400, + status: 200, + url: 'https://example.com/bar', + }, + { + method: 'GET', + queueMs: 10, + reqMs: 100, + status: 200, + url: 'https://example.com/foo', + }, + { + method: 'GET', + queueMs: 20, + reqMs: 200, + status: 200, + url: 'https://example.com/foo', + }, + { + method: 'GET', + queueMs: 80, + reqMs: 800, + status: 404, + url: 'https://example.com/foo', + }, + ], + }, + hosts: { + 'example.com': { + count: 4, + queueAvgMs: 38, + queueMaxMs: 80, + queueMedianMs: 40, + reqAvgMs: 375, + reqMaxMs: 800, + reqMedianMs: 400, + }, + }, + rawRequests: [ + 'GET https://example.com/bar 200 400 40', + 'GET https://example.com/foo 200 100 10', + 'GET https://example.com/foo 200 200 20', + 'GET https://example.com/foo 404 800 80', + ], + requests: 5, + urls: { + 'https://example.com/bar': { + GET: { + '200': 1, + }, + }, + 'https://example.com/foo': { + GET: { + '200': 2, + '404': 1, + }, + }, + }, + }); + }); + + it('logs report', () => { + HttpStats.write({ + method: 'GET', + url: 'https://example.com/foo', + reqMs: 100, + queueMs: 10, + status: 200, + }); + HttpStats.write({ + method: 'GET', + url: 'https://example.com/foo', + reqMs: 200, + queueMs: 20, + status: 200, + }); + HttpStats.write({ + method: 'GET', + url: 'https://example.com/bar', + reqMs: 400, + queueMs: 40, + status: 200, + }); + HttpStats.write({ + method: 'GET', + url: 'https://example.com/foo', + reqMs: 800, + queueMs: 80, + status: 404, + }); + + HttpStats.report(); + + expect(logger.logger.trace).toHaveBeenCalledTimes(1); + const [traceData, traceMsg] = logger.logger.trace.mock.calls[0]; + expect(traceMsg).toBe('HTTP full statistics'); + expect(traceData).toEqual({ + rawRequests: [ + 'GET https://example.com/bar 200 400 40', + 'GET https://example.com/foo 200 100 10', + 'GET https://example.com/foo 200 200 20', + 'GET https://example.com/foo 404 800 80', + ], + hostRequests: { + 'example.com': [ + { + method: 'GET', + queueMs: 40, + reqMs: 400, + status: 200, + url: 'https://example.com/bar', + }, + { + method: 'GET', + queueMs: 10, + reqMs: 100, + status: 200, + url: 'https://example.com/foo', + }, + { + method: 'GET', + queueMs: 20, + reqMs: 200, + status: 200, + url: 'https://example.com/foo', + }, + { + method: 'GET', + queueMs: 80, + reqMs: 800, + status: 404, + url: 'https://example.com/foo', + }, + ], + }, + }); + + expect(logger.logger.debug).toHaveBeenCalledTimes(1); + const [debugData, debugMsg] = logger.logger.debug.mock.calls[0]; + expect(debugMsg).toBe('HTTP statistics'); + expect(debugData).toEqual({ + hosts: { + 'example.com': { + count: 4, + queueAvgMs: 38, + queueMaxMs: 80, + queueMedianMs: 40, + reqAvgMs: 375, + reqMaxMs: 800, + reqMedianMs: 400, + }, + }, + requests: 4, + urls: { + 'https://example.com/bar': { + GET: { + '200': 1, + }, + }, + 'https://example.com/foo': { + GET: { + '200': 2, + '404': 1, + }, + }, + }, + }); + }); + }); + + describe('HttpCacheStats', () => { + it('returns empty data', () => { + const res = HttpCacheStats.getData(); + expect(res).toEqual({}); + }); + + it('ignores wrong url', () => { + HttpCacheStats.incLocalHits(''); + expect(HttpCacheStats.getData()).toEqual({}); + }); + + it('writes data points', () => { + HttpCacheStats.incLocalHits('https://example.com/foo'); + HttpCacheStats.incLocalHits('https://example.com/foo'); + HttpCacheStats.incLocalMisses('https://example.com/foo'); + HttpCacheStats.incLocalMisses('https://example.com/bar'); + HttpCacheStats.incRemoteHits('https://example.com/bar'); + HttpCacheStats.incRemoteMisses('https://example.com/bar'); + + const res = HttpCacheStats.getData(); + + expect(res).toEqual({ + 'https://example.com/bar': { + hit: 1, + miss: 1, + localMiss: 1, + }, + 'https://example.com/foo': { + hit: 0, + miss: 0, + localHit: 2, + localMiss: 1, + }, + }); + }); + + it('prints report', () => { + HttpCacheStats.incLocalHits('https://example.com/foo'); + HttpCacheStats.incLocalHits('https://example.com/foo'); + HttpCacheStats.incLocalMisses('https://example.com/foo'); + HttpCacheStats.incLocalMisses('https://example.com/bar'); + HttpCacheStats.incRemoteHits('https://example.com/bar'); + HttpCacheStats.incRemoteMisses('https://example.com/bar'); + + HttpCacheStats.report(); + + expect(logger.logger.debug).toHaveBeenCalledTimes(1); + const [data, msg] = logger.logger.debug.mock.calls[0]; + expect(msg).toBe('HTTP cache statistics'); + expect(data).toEqual({ + 'https://example.com': { + '/foo': { hit: 0, localHit: 2, localMiss: 1, miss: 0 }, + '/bar': { hit: 1, localMiss: 1, miss: 1 }, + }, + }); + }); + }); +}); diff --git a/lib/util/stats.ts b/lib/util/stats.ts new file mode 100644 index 00000000000000..48267fff3ba677 --- /dev/null +++ b/lib/util/stats.ts @@ -0,0 +1,348 @@ +import { logger } from '../logger'; +import * as memCache from './cache/memory'; +import { parseUrl } from './url'; + +type LookupStatsData = Record; + +interface TimingStatsReport { + count: number; + avgMs: number; + medianMs: number; + maxMs: number; + totalMs: number; +} + +export function makeTimingReport(data: number[]): TimingStatsReport { + const count = data.length; + const totalMs = data.reduce((a, c) => a + c, 0); + const avgMs = count ? Math.round(totalMs / count) : 0; + const maxMs = Math.max(0, ...data); + const sorted = data.sort((a, b) => a - b); + const medianMs = count ? sorted[Math.floor(count / 2)] : 0; + return { count, avgMs, medianMs, maxMs, totalMs }; +} + +export class LookupStats { + static write(datasource: string, duration: number): void { + const data = memCache.get('lookup-stats') ?? {}; + data[datasource] ??= []; + data[datasource].push(duration); + memCache.set('lookup-stats', data); + } + + static async wrap( + datasource: string, + callback: () => Promise, + ): Promise { + const start = Date.now(); + const result = await callback(); + const duration = Date.now() - start; + LookupStats.write(datasource, duration); + return result; + } + + static getReport(): Record { + const report: Record = {}; + const data = memCache.get('lookup-stats') ?? {}; + for (const [datasource, durations] of Object.entries(data)) { + report[datasource] = makeTimingReport(durations); + } + return report; + } + + static report(): void { + const report = LookupStats.getReport(); + logger.debug(report, 'Lookup statistics'); + } +} + +type PackageCacheData = number[]; + +export class PackageCacheStats { + static writeSet(duration: number): void { + const data = memCache.get('package-cache-sets') ?? []; + data.push(duration); + memCache.set('package-cache-sets', data); + } + + static async wrapSet(callback: () => Promise): Promise { + const start = Date.now(); + const result = await callback(); + const duration = Date.now() - start; + PackageCacheStats.writeSet(duration); + return result; + } + + static writeGet(duration: number): void { + const data = memCache.get('package-cache-gets') ?? []; + data.push(duration); + memCache.set('package-cache-gets', data); + } + + static async wrapGet(callback: () => Promise): Promise { + const start = Date.now(); + const result = await callback(); + const duration = Date.now() - start; + PackageCacheStats.writeGet(duration); + return result; + } + + static getReport(): { get: TimingStatsReport; set: TimingStatsReport } { + const packageCacheGets = + memCache.get('package-cache-gets') ?? []; + const get = makeTimingReport(packageCacheGets); + + const packageCacheSets = + memCache.get('package-cache-sets') ?? []; + const set = makeTimingReport(packageCacheSets); + + return { get, set }; + } + + static report(): void { + const report = PackageCacheStats.getReport(); + logger.debug(report, 'Package cache statistics'); + } +} + +export interface HttpRequestStatsDataPoint { + method: string; + url: string; + reqMs: number; + queueMs: number; + status: number; +} + +interface HostStatsData { + count: number; + reqAvgMs: number; + reqMedianMs: number; + reqMaxMs: number; + queueAvgMs: number; + queueMedianMs: number; + queueMaxMs: number; +} + +// url -> method -> status -> count +type UrlHttpStat = Record>>; + +interface HttpStatsCollection { + // debug data + urls: UrlHttpStat; + hosts: Record; + requests: number; + + // trace data + rawRequests: string[]; + hostRequests: Record; +} + +export class HttpStats { + static write(data: HttpRequestStatsDataPoint): void { + const httpRequests = + memCache.get('http-requests') ?? []; + httpRequests.push(data); + memCache.set('http-requests', httpRequests); + } + + static getDataPoints(): HttpRequestStatsDataPoint[] { + const httpRequests = + memCache.get('http-requests') ?? []; + + // istanbul ignore next: sorting is hard and not worth testing + httpRequests.sort((a, b) => { + if (a.url < b.url) { + return -1; + } + + if (a.url > b.url) { + return 1; + } + + return 0; + }); + + return httpRequests; + } + + static getReport(): HttpStatsCollection { + const dataPoints = HttpStats.getDataPoints(); + + const requests = dataPoints.length; + + const urls: UrlHttpStat = {}; + const rawRequests: string[] = []; + const hostRequests: Record = {}; + + for (const dataPoint of dataPoints) { + const { url, reqMs, queueMs, status } = dataPoint; + const method = dataPoint.method.toUpperCase(); + + const parsedUrl = parseUrl(url); + if (!parsedUrl) { + logger.debug({ url }, 'Failed to parse URL during stats reporting'); + continue; + } + const { hostname, origin, pathname } = parsedUrl; + const baseUrl = `${origin}${pathname}`; + + urls[baseUrl] ??= {}; + urls[baseUrl][method] ??= {}; + urls[baseUrl][method][status] ??= 0; + urls[baseUrl][method][status] += 1; + + rawRequests.push(`${method} ${url} ${status} ${reqMs} ${queueMs}`); + + hostRequests[hostname] ??= []; + hostRequests[hostname].push(dataPoint); + } + + const hosts: Record = {}; + + for (const [hostname, dataPoints] of Object.entries(hostRequests)) { + const count = dataPoints.length; + + const reqTimes = dataPoints.map((r) => r.reqMs); + const queueTimes = dataPoints.map((r) => r.queueMs); + + const reqReport = makeTimingReport(reqTimes); + const queueReport = makeTimingReport(queueTimes); + + hosts[hostname] = { + count, + reqAvgMs: reqReport.avgMs, + reqMedianMs: reqReport.medianMs, + reqMaxMs: reqReport.maxMs, + queueAvgMs: queueReport.avgMs, + queueMedianMs: queueReport.medianMs, + queueMaxMs: queueReport.maxMs, + }; + } + + return { + urls, + rawRequests, + hostRequests, + hosts, + requests, + }; + } + + static report(): void { + const { urls, rawRequests, hostRequests, hosts, requests } = + HttpStats.getReport(); + logger.trace({ rawRequests, hostRequests }, 'HTTP full statistics'); + logger.debug({ urls, hosts, requests }, 'HTTP statistics'); + } +} + +interface HttpCacheHostStatsData { + hit: number; + miss: number; + localHit?: number; + localMiss?: number; +} + +type HttpCacheStatsData = Record; + +function sortObject(obj: Record): Record { + const result: Record = {}; + for (const key of Object.keys(obj).sort()) { + result[key] = obj[key]; + } + return result; +} + +export class HttpCacheStats { + static getData(): HttpCacheStatsData { + return memCache.get('http-cache-stats') ?? {}; + } + + static read(key: string): HttpCacheHostStatsData { + return ( + this.getData()?.[key] ?? { + hit: 0, + miss: 0, + } + ); + } + + static write(key: string, data: HttpCacheHostStatsData): void { + const stats = memCache.get('http-cache-stats') ?? {}; + stats[key] = data; + memCache.set('http-cache-stats', stats); + } + + static getBaseUrl(url: string): string | null { + const parsedUrl = parseUrl(url); + if (!parsedUrl) { + logger.debug({ url }, 'Failed to parse URL during cache stats'); + return null; + } + const { origin, pathname } = parsedUrl; + const baseUrl = `${origin}${pathname}`; + return baseUrl; + } + + static incLocalHits(url: string): void { + const baseUrl = HttpCacheStats.getBaseUrl(url); + if (baseUrl) { + const host = baseUrl; + const stats = HttpCacheStats.read(host); + stats.localHit ??= 0; + stats.localHit += 1; + HttpCacheStats.write(host, stats); + } + } + + static incLocalMisses(url: string): void { + const baseUrl = HttpCacheStats.getBaseUrl(url); + if (baseUrl) { + const host = baseUrl; + const stats = HttpCacheStats.read(host); + stats.localMiss ??= 0; + stats.localMiss += 1; + HttpCacheStats.write(host, stats); + } + } + + static incRemoteHits(url: string): void { + const baseUrl = HttpCacheStats.getBaseUrl(url); + if (baseUrl) { + const host = baseUrl; + const stats = HttpCacheStats.read(host); + stats.hit += 1; + HttpCacheStats.write(host, stats); + } + } + + static incRemoteMisses(url: string): void { + const baseUrl = HttpCacheStats.getBaseUrl(url); + if (baseUrl) { + const host = baseUrl; + const stats = HttpCacheStats.read(host); + stats.miss += 1; + HttpCacheStats.write(host, stats); + } + } + + static report(): void { + const data = HttpCacheStats.getData(); + let report: Record> = {}; + for (const [url, stats] of Object.entries(data)) { + const parsedUrl = parseUrl(url); + if (parsedUrl) { + const { origin, pathname } = parsedUrl; + report[origin] ??= {}; + report[origin][pathname] = stats; + } + } + + for (const [host, hostStats] of Object.entries(report)) { + report[host] = sortObject(hostStats); + } + report = sortObject(report); + + logger.debug(report, 'HTTP cache statistics'); + } +} diff --git a/lib/util/template/index.spec.ts b/lib/util/template/index.spec.ts index 0b86af09154cf1..4c1be2e68877a3 100644 --- a/lib/util/template/index.spec.ts +++ b/lib/util/template/index.spec.ts @@ -276,4 +276,50 @@ describe('util/template/index', () => { expect(output).toBe('not equals'); }); }); + + describe('includes', () => { + it('includes is true', () => { + const output = template.compile( + '{{#if (includes labels "dependencies")}}production{{else}}notProduction{{/if}}', + { + labels: ['dependencies'], + }, + ); + + expect(output).toBe('production'); + }); + + it('includes is false', () => { + const output = template.compile( + '{{#if (includes labels "dependencies")}}production{{else}}notProduction{{/if}}', + { + labels: ['devDependencies'], + }, + ); + + expect(output).toBe('notProduction'); + }); + + it('includes with incorrect type first argument', () => { + const output = template.compile( + '{{#if (includes labels "dependencies")}}production{{else}}notProduction{{/if}}', + { + labels: 'devDependencies', + }, + ); + + expect(output).toBe('notProduction'); + }); + + it('includes with incorrect type second argument', () => { + const output = template.compile( + '{{#if (includes labels 555)}}production{{else}}notProduction{{/if}}', + { + labels: ['devDependencies'], + }, + ); + + expect(output).toBe('notProduction'); + }); + }); }); diff --git a/lib/util/template/index.ts b/lib/util/template/index.ts index 66ff1847ed1069..c9e9306ff92d16 100644 --- a/lib/util/template/index.ts +++ b/lib/util/template/index.ts @@ -28,6 +28,14 @@ handlebars.registerHelper('containsString', (str, subStr) => handlebars.registerHelper('equals', (arg1, arg2) => arg1 === arg2); +handlebars.registerHelper('includes', (arg1: string[], arg2: string) => { + if (is.array(arg1, is.string) && is.string(arg2)) { + return arg1.includes(arg2); + } + + return false; +}); + handlebars.registerHelper({ and(...args) { // Need to remove the 'options', as last parameter @@ -88,6 +96,8 @@ export const allowedFields = { depNameSanitized: 'The depName field sanitized for use in branches after removing spaces and special characters', depType: 'The dependency type (if extracted - manager-dependent)', + depTypes: + 'A deduplicated array of dependency types (if extracted - manager-dependent) in a branch', displayFrom: 'The current value, formatted for display', displayPending: 'Latest pending update, if internalChecksFilter is in use', displayTo: 'The to value, formatted for display', @@ -123,8 +133,10 @@ export const allowedFields = { packageFileDir: 'The directory with full path where the packageFile was found', packageName: 'The full name that was used to look up the dependency', + packageScope: 'The scope of the package name. Supports Maven group ID only', parentDir: 'The name of the directory that the dependency was found in, without full path', + parentOrg: 'The name of the parent organization for the current repository', platform: 'VCS platform in use, e.g. "github", "gitlab", etc.', prettyDepType: 'Massaged depType', prettyNewMajor: 'The new major value with v prepended to it.', @@ -141,6 +153,8 @@ export const allowedFields = { sourceRepoOrg: 'The repository organization in the sourceUrl, if present', sourceRepoSlug: 'The slugified pathname of the sourceUrl, if present', sourceUrl: 'The source URL for the package', + topLevelOrg: + 'The name of the top-level organization for the current repository', updateType: 'One of digest, pin, rollback, patch, minor, major, replacement, pinDigest', upgrades: 'An array of upgrade objects in the branch', diff --git a/lib/util/url.spec.ts b/lib/util/url.spec.ts index cbc94643727554..a0c65cb7ae27b1 100644 --- a/lib/util/url.spec.ts +++ b/lib/util/url.spec.ts @@ -3,6 +3,7 @@ import { ensurePathPrefix, ensureTrailingSlash, getQueryString, + isHttpUrl, joinUrlParts, parseLinkHeader, parseUrl, @@ -10,7 +11,6 @@ import { resolveBaseUrl, trimSlashes, trimTrailingSlash, - validateUrl, } from './url'; describe('util/url', () => { @@ -97,15 +97,14 @@ describe('util/url', () => { expect(getQueryString({ a: 1, b: [1, 2] })).toBe('a=1&b=1&b=2'); }); - it('validates URLs', () => { - expect(validateUrl(undefined)).toBeFalse(); - expect(validateUrl('')).toBeFalse(); - expect(validateUrl(null)).toBeFalse(); - expect(validateUrl('foo')).toBeFalse(); - expect(validateUrl('ssh://github.com')).toBeFalse(); - expect(validateUrl('http://github.com')).toBeTrue(); - expect(validateUrl('https://github.com')).toBeTrue(); - expect(validateUrl('https://github.com', false)).toBeTrue(); + it('validates http-based URLs', () => { + expect(isHttpUrl(undefined)).toBeFalse(); + expect(isHttpUrl('')).toBeFalse(); + expect(isHttpUrl(null)).toBeFalse(); + expect(isHttpUrl('foo')).toBeFalse(); + expect(isHttpUrl('ssh://github.com')).toBeFalse(); + expect(isHttpUrl('http://github.com')).toBeTrue(); + expect(isHttpUrl('https://github.com')).toBeTrue(); }); it('parses URL', () => { diff --git a/lib/util/url.ts b/lib/util/url.ts index c28d421e41c389..9ac957233127ff 100644 --- a/lib/util/url.ts +++ b/lib/util/url.ts @@ -85,16 +85,13 @@ export function getQueryString(params: Record): string { return usp.toString(); } -export function validateUrl( - url: string | null | undefined, - httpOnly = true, -): boolean { +export function isHttpUrl(url: unknown): boolean { if (!is.nonEmptyString(url)) { return false; } try { const { protocol } = new URL(url); - return httpOnly ? !!protocol.startsWith('http') : !!protocol; + return protocol === 'https:' || protocol === 'http:'; } catch (err) { return false; } diff --git a/lib/util/yaml.ts b/lib/util/yaml.ts index c0bceac4b5f127..1a6ed8cce7ae2a 100644 --- a/lib/util/yaml.ts +++ b/lib/util/yaml.ts @@ -79,8 +79,9 @@ export function dump(obj: any, opts?: DumpOptions | undefined): string { function massageContent(content: string, options?: YamlOptions): string { if (options?.removeTemplates) { return content + .replace(regEx(/\s+{{.+?}}:.+/gs), '') .replace(regEx(/{{`.+?`}}/gs), '') - .replace(regEx(/{{.+?}}/g), '') + .replace(regEx(/{{.+?}}/gs), '') .replace(regEx(/{%`.+?`%}/gs), '') .replace(regEx(/{%.+?%}/g), ''); } diff --git a/lib/workers/global/autodiscover.ts b/lib/workers/global/autodiscover.ts index ed74f350f3d95e..e4da1e8c403b5b 100644 --- a/lib/workers/global/autodiscover.ts +++ b/lib/workers/global/autodiscover.ts @@ -38,8 +38,11 @@ export async function autodiscoverRepositories( // Autodiscover list of repositories let discovered = await platform.getRepos({ topics: config.autodiscoverTopics, + sort: config.autodiscoverRepoSort, + order: config.autodiscoverRepoOrder, includeMirrors: config.includeMirrors, namespaces: config.autodiscoverNamespaces, + projects: config.autodiscoverProjects, }); if (!discovered?.length) { // Soft fail (no error thrown) if no accessible repositories diff --git a/lib/workers/global/config/parse/env.spec.ts b/lib/workers/global/config/parse/env.spec.ts index 767624090e850a..4efcc8cc1d526c 100644 --- a/lib/workers/global/config/parse/env.spec.ts +++ b/lib/workers/global/config/parse/env.spec.ts @@ -5,27 +5,27 @@ import type { ParseConfigOptions } from './types'; describe('workers/global/config/parse/env', () => { describe('.getConfig(env)', () => { - it('returns empty env', () => { - expect(env.getConfig({})).toEqual({ hostRules: [] }); + it('returns empty env', async () => { + expect(await env.getConfig({})).toEqual({ hostRules: [] }); }); - it('supports boolean true', () => { + it('supports boolean true', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_CONFIG_MIGRATION: 'true' }; - expect(env.getConfig(envParam).configMigration).toBeTrue(); + expect((await env.getConfig(envParam)).configMigration).toBeTrue(); }); - it('supports boolean false', () => { + it('supports boolean false', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_CONFIG_MIGRATION: 'false', }; - expect(env.getConfig(envParam).configMigration).toBeFalse(); + expect((await env.getConfig(envParam)).configMigration).toBeFalse(); }); - it('throws exception for invalid boolean value', () => { + it('throws exception for invalid boolean value', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_CONFIG_MIGRATION: 'badvalue', }; - expect(() => env.getConfig(envParam)).toThrow( + await expect(env.getConfig(envParam)).rejects.toThrow( Error( "Invalid boolean value: expected 'true' or 'false', but got 'badvalue'", ), @@ -34,54 +34,54 @@ describe('workers/global/config/parse/env', () => { delete process.env.RENOVATE_CONFIG_MIGRATION; - it('supports list single', () => { + it('supports list single', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_LABELS: 'a' }; - expect(env.getConfig(envParam).labels).toEqual(['a']); + expect((await env.getConfig(envParam)).labels).toEqual(['a']); }); - it('supports list multiple', () => { + it('supports list multiple', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_LABELS: 'a,b,c' }; - expect(env.getConfig(envParam).labels).toEqual(['a', 'b', 'c']); + expect((await env.getConfig(envParam)).labels).toEqual(['a', 'b', 'c']); }); - it('supports list multiple without blank items', () => { + it('supports list multiple without blank items', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_LABELS: 'a,b,c,' }; - expect(env.getConfig(envParam).labels).toEqual(['a', 'b', 'c']); + expect((await env.getConfig(envParam)).labels).toEqual(['a', 'b', 'c']); }); - it('supports string', () => { + it('supports string', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_TOKEN: 'a' }; - expect(env.getConfig(envParam).token).toBe('a'); + expect((await env.getConfig(envParam)).token).toBe('a'); }); - it('coerces string newlines', () => { + it('coerces string newlines', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_GIT_PRIVATE_KEY: 'abc\\ndef', }; - expect(env.getConfig(envParam).gitPrivateKey).toBe('abc\ndef'); + expect((await env.getConfig(envParam)).gitPrivateKey).toBe('abc\ndef'); }); - it('supports custom prefixes', () => { + it('supports custom prefixes', async () => { const envParam: NodeJS.ProcessEnv = { ENV_PREFIX: 'FOOBAR_', FOOBAR_TOKEN: 'abc', }; - const res = env.getConfig(envParam); + const res = await env.getConfig(envParam); expect(res).toMatchObject({ token: 'abc' }); }); - it('supports json', () => { + it('supports json', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_LOCK_FILE_MAINTENANCE: '{}', }; - expect(env.getConfig(envParam).lockFileMaintenance).toEqual({}); + expect((await env.getConfig(envParam)).lockFileMaintenance).toEqual({}); }); - it('supports arrays of objects', () => { + it('supports arrays of objects', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_HOST_RULES: JSON.stringify([{ foo: 'bar' }]), }; - const res = env.getConfig(envParam); + const res = await env.getConfig(envParam); expect(res).toMatchObject({ hostRules: [{ foo: 'bar' }] }); }); @@ -92,16 +92,16 @@ describe('workers/global/config/parse/env', () => { ${{ RENOVATE_RECREATE_WHEN: 'auto' }} | ${{ recreateWhen: 'auto' }} ${{ RENOVATE_RECREATE_WHEN: 'always' }} | ${{ recreateWhen: 'always' }} ${{ RENOVATE_RECREATE_WHEN: 'never' }} | ${{ recreateWhen: 'never' }} - `('"$envArg" -> $config', ({ envArg, config }) => { - expect(env.getConfig(envArg)).toMatchObject(config); + `('"$envArg" -> $config', async ({ envArg, config }) => { + expect(await env.getConfig(envArg)).toMatchObject(config); }); - it('skips misconfigured arrays', () => { + it('skips misconfigured arrays', async () => { const envName = 'RENOVATE_HOST_RULES'; const val = JSON.stringify('foobar'); const envParam: NodeJS.ProcessEnv = { [envName]: val }; - const res = env.getConfig(envParam); + const res = await env.getConfig(envParam); expect(res).toEqual({ hostRules: [] }); expect(logger.debug).toHaveBeenLastCalledWith( @@ -110,12 +110,12 @@ describe('workers/global/config/parse/env', () => { ); }); - it('skips garbage array values', () => { + it('skips garbage array values', async () => { const envName = 'RENOVATE_HOST_RULES'; const val = '!@#'; const envParam: NodeJS.ProcessEnv = { [envName]: val }; - const res = env.getConfig(envParam); + const res = await env.getConfig(envParam); expect(res).toEqual({ hostRules: [] }); expect(logger.debug).toHaveBeenLastCalledWith( @@ -124,31 +124,31 @@ describe('workers/global/config/parse/env', () => { ); }); - it('supports GitHub token', () => { + it('supports GitHub token', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_TOKEN: 'github.com token', }; - expect(env.getConfig(envParam)).toMatchSnapshot({ + expect(await env.getConfig(envParam)).toMatchSnapshot({ token: 'github.com token', }); }); - it('supports GitHub custom endpoint', () => { + it('supports GitHub custom endpoint', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_ENDPOINT: 'a ghe endpoint', }; - expect(env.getConfig(envParam)).toMatchSnapshot({ + expect(await env.getConfig(envParam)).toMatchSnapshot({ endpoint: 'a ghe endpoint', }); }); - it('supports GitHub custom endpoint and github.com', () => { + it('supports GitHub custom endpoint and github.com', async () => { const envParam: NodeJS.ProcessEnv = { GITHUB_COM_TOKEN: 'a github.com token', RENOVATE_ENDPOINT: 'a ghe endpoint', RENOVATE_TOKEN: 'a ghe token', }; - expect(env.getConfig(envParam)).toMatchSnapshot({ + expect(await env.getConfig(envParam)).toMatchSnapshot({ endpoint: 'a ghe endpoint', hostRules: [ { @@ -161,12 +161,12 @@ describe('workers/global/config/parse/env', () => { }); }); - it('supports GitHub fine-grained PATs', () => { + it('supports GitHub fine-grained PATs', async () => { const envParam: NodeJS.ProcessEnv = { GITHUB_COM_TOKEN: 'github_pat_XXXXXX', RENOVATE_TOKEN: 'a github.com token', }; - expect(env.getConfig(envParam)).toEqual({ + expect(await env.getConfig(envParam)).toEqual({ token: 'a github.com token', hostRules: [ { @@ -178,62 +178,62 @@ describe('workers/global/config/parse/env', () => { }); }); - it('supports GitHub custom endpoint and gitlab.com', () => { + it('supports GitHub custom endpoint and gitlab.com', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_ENDPOINT: 'a ghe endpoint', RENOVATE_TOKEN: 'a ghe token', }; - expect(env.getConfig(envParam)).toMatchSnapshot({ + expect(await env.getConfig(envParam)).toMatchSnapshot({ endpoint: 'a ghe endpoint', token: 'a ghe token', }); }); - it('supports GitLab token', () => { + it('supports GitLab token', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_PLATFORM: 'gitlab', RENOVATE_TOKEN: 'a gitlab.com token', }; - expect(env.getConfig(envParam)).toMatchSnapshot({ + expect(await env.getConfig(envParam)).toMatchSnapshot({ platform: 'gitlab', token: 'a gitlab.com token', }); }); - it('supports GitLab custom endpoint', () => { + it('supports GitLab custom endpoint', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_PLATFORM: 'gitlab', RENOVATE_TOKEN: 'a gitlab token', RENOVATE_ENDPOINT: 'a gitlab endpoint', }; - expect(env.getConfig(envParam)).toMatchSnapshot({ + expect(await env.getConfig(envParam)).toMatchSnapshot({ endpoint: 'a gitlab endpoint', platform: 'gitlab', token: 'a gitlab token', }); }); - it('supports Azure DevOps', () => { + it('supports Azure DevOps', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_PLATFORM: 'azure', RENOVATE_TOKEN: 'an Azure DevOps token', RENOVATE_ENDPOINT: 'an Azure DevOps endpoint', }; - expect(env.getConfig(envParam)).toMatchSnapshot({ + expect(await env.getConfig(envParam)).toMatchSnapshot({ endpoint: 'an Azure DevOps endpoint', platform: 'azure', token: 'an Azure DevOps token', }); }); - it('supports Bitbucket token', () => { + it('supports Bitbucket token', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_PLATFORM: 'bitbucket', RENOVATE_ENDPOINT: 'a bitbucket endpoint', RENOVATE_USERNAME: 'some-username', RENOVATE_PASSWORD: 'app-password', }; - expect(env.getConfig(envParam)).toMatchSnapshot({ + expect(await env.getConfig(envParam)).toMatchSnapshot({ platform: 'bitbucket', endpoint: 'a bitbucket endpoint', username: 'some-username', @@ -241,14 +241,14 @@ describe('workers/global/config/parse/env', () => { }); }); - it('supports Bitbucket username/password', () => { + it('supports Bitbucket username/password', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_PLATFORM: 'bitbucket', RENOVATE_ENDPOINT: 'a bitbucket endpoint', RENOVATE_USERNAME: 'some-username', RENOVATE_PASSWORD: 'app-password', }; - expect(env.getConfig(envParam)).toMatchSnapshot({ + expect(await env.getConfig(envParam)).toMatchSnapshot({ endpoint: 'a bitbucket endpoint', hostRules: [], password: 'app-password', @@ -257,42 +257,76 @@ describe('workers/global/config/parse/env', () => { }); }); - it('merges full config from env', () => { + it('merges full config from env', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_CONFIG: '{"enabled":false,"token":"foo"}', RENOVATE_TOKEN: 'a', }; - const config = env.getConfig(envParam); + const config = await env.getConfig(envParam); expect(config.enabled).toBeFalse(); expect(config.token).toBe('a'); }); - describe('malformed RENOVATE_CONFIG', () => { + it('massages converted experimental env vars', async () => { + const envParam: NodeJS.ProcessEnv = { + RENOVATE_X_MERGE_CONFIDENCE_API_BASE_URL: 'some-url', // converted + RENOVATE_X_MERGE_CONFIDENCE_SUPPORTED_DATASOURCES: '["docker"]', // converted + RENOVATE_X_AUTODISCOVER_REPO_SORT: 'alpha', + RENOVATE_X_DOCKER_MAX_PAGES: '10', + RENOVATE_AUTODISCOVER_REPO_ORDER: 'desc', + }; + const config = await env.getConfig(envParam); + expect(config).toMatchObject({ + mergeConfidenceEndpoint: 'some-url', + mergeConfidenceDatasources: ['docker'], + autodiscoverRepoSort: 'alpha', + autodiscoverRepoOrder: 'desc', + }); + }); + + describe('RENOVATE_CONFIG tests', () => { let processExit: jest.SpyInstance; beforeAll(() => { processExit = jest .spyOn(process, 'exit') - .mockImplementation((() => {}) as never); + .mockImplementation((async () => {}) as never); }); afterAll(() => { processExit.mockRestore(); }); - it('crashes', () => { + it('crashes', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_CONFIG: '!@#' }; - env.getConfig(envParam); + await env.getConfig(envParam); expect(processExit).toHaveBeenCalledWith(1); }); + + it('migrates RENOVATE_CONFIG', async () => { + const envParam: NodeJS.ProcessEnv = { + RENOVATE_CONFIG: '{"automerge":"any","token":"foo"}', + }; + const config = await env.getConfig(envParam); + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(config.automerge).toBeTrue(); + }); + + it('warns if config in RENOVATE_CONFIG is invalid', async () => { + const envParam: NodeJS.ProcessEnv = { + RENOVATE_CONFIG: '{"enabled":"invalid-value","prTitle":"something"}', + }; + await env.getConfig(envParam); + expect(logger.warn).toHaveBeenCalledTimes(2); + }); }); describe('migrations', () => { - it('renames migrated variables', () => { + it('renames migrated variables', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_GIT_LAB_AUTOMERGE: 'true', }; - const config = env.getConfig(envParam); + const config = await env.getConfig(envParam); expect(config.platformAutomerge).toBe(true); }); }); @@ -322,43 +356,43 @@ describe('workers/global/config/parse/env', () => { expect(env.getEnvName(option)).toBe('RENOVATE_ONE_TWO_THREE'); }); - it('dryRun boolean true', () => { + it('dryRun boolean true', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_DRY_RUN: 'true', }; - const config = env.getConfig(envParam); + const config = await env.getConfig(envParam); expect(config.dryRun).toBe('full'); }); - it('dryRun boolean false', () => { + it('dryRun boolean false', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_DRY_RUN: 'false', }; - const config = env.getConfig(envParam); + const config = await env.getConfig(envParam); expect(config.dryRun).toBeUndefined(); }); - it('dryRun null', () => { + it('dryRun null', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_DRY_RUN: 'null', }; - const config = env.getConfig(envParam); + const config = await env.getConfig(envParam); expect(config.dryRun).toBeUndefined(); }); - it('requireConfig boolean true', () => { + it('requireConfig boolean true', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_REQUIRE_CONFIG: 'true' as RequiredConfig, }; - const config = env.getConfig(envParam); + const config = await env.getConfig(envParam); expect(config.requireConfig).toBe('required'); }); - it('requireConfig boolean false', () => { + it('requireConfig boolean false', async () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_REQUIRE_CONFIG: 'false' as RequiredConfig, }; - const config = env.getConfig(envParam); + const config = await env.getConfig(envParam); expect(config.requireConfig).toBe('optional'); }); }); diff --git a/lib/workers/global/config/parse/env.ts b/lib/workers/global/config/parse/env.ts index 4db7bf7e9cac11..b49cee0cbaeb89 100644 --- a/lib/workers/global/config/parse/env.ts +++ b/lib/workers/global/config/parse/env.ts @@ -5,6 +5,7 @@ import type { AllConfig } from '../../../../config/types'; import { logger } from '../../../../logger'; import { coersions } from './coersions'; import type { ParseConfigOptions } from './types'; +import { migrateAndValidateConfig } from './util'; function normalizePrefixes( env: NodeJS.ProcessEnv, @@ -38,6 +39,8 @@ const renameKeys = { aliases: 'registryAliases', azureAutoComplete: 'platformAutomerge', // migrate: azureAutoComplete gitLabAutomerge: 'platformAutomerge', // migrate: gitLabAutomerge + mergeConfidenceApiBaseUrl: 'mergeConfidenceEndpoint', + mergeConfidenceSupportedDatasources: 'mergeConfidenceDatasources', }; function renameEnvKeys(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv { @@ -82,9 +85,38 @@ function massageEnvKeyValues(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv { return result; } -export function getConfig(inputEnv: NodeJS.ProcessEnv): AllConfig { +const convertedExperimentalEnvVars = [ + 'RENOVATE_X_AUTODISCOVER_REPO_SORT', + 'RENOVATE_X_AUTODISCOVER_REPO_ORDER', + 'RENOVATE_X_MERGE_CONFIDENCE_API_BASE_URL', + 'RENOVATE_X_MERGE_CONFIDENCE_SUPPORTED_DATASOURCES', +]; + +/** + * Massages the experimental env vars which have been converted to config options + * + * e.g. RENOVATE_X_AUTODISCOVER_REPO_SORT -> RENOVATE_AUTODISCOVER_REPO_SORT + */ +function massageConvertedExperimentalVars( + env: NodeJS.ProcessEnv, +): NodeJS.ProcessEnv { + const result = { ...env }; + for (const key of convertedExperimentalEnvVars) { + if (env[key] !== undefined) { + const newKey = key.replace('RENOVATE_X_', 'RENOVATE_'); + result[newKey] = env[key]; + delete result[key]; + } + } + return result; +} + +export async function getConfig( + inputEnv: NodeJS.ProcessEnv, +): Promise { let env = inputEnv; env = normalizePrefixes(inputEnv, inputEnv.ENV_PREFIX); + env = massageConvertedExperimentalVars(env); env = renameEnvKeys(env); // massage the values of migrated configuration keys env = massageEnvKeyValues(env); @@ -97,6 +129,8 @@ export function getConfig(inputEnv: NodeJS.ProcessEnv): AllConfig { try { config = JSON5.parse(env.RENOVATE_CONFIG); logger.debug({ config }, 'Detected config in env RENOVATE_CONFIG'); + + config = await migrateAndValidateConfig(config, 'RENOVATE_CONFIG'); } catch (err) { logger.fatal({ err }, 'Could not parse RENOVATE_CONFIG'); process.exit(1); diff --git a/lib/workers/global/config/parse/file.spec.ts b/lib/workers/global/config/parse/file.spec.ts index 921a708f043279..822f67bda24e1a 100644 --- a/lib/workers/global/config/parse/file.spec.ts +++ b/lib/workers/global/config/parse/file.spec.ts @@ -53,6 +53,18 @@ describe('workers/global/config/parse/file', () => { expect(res.rangeStrategy).toBe('bump'); }); + it('warns if config is invalid', async () => { + const configFile = upath.resolve(tmp.path, 'config.js'); + const fileContent = `module.exports = { + "enabled": "invalid-value", + "prTitle":"something", + };`; + fs.writeFileSync(configFile, fileContent, { encoding: 'utf8' }); + await file.getConfig({ RENOVATE_CONFIG_FILE: configFile }); + expect(logger.warn).toHaveBeenCalledTimes(2); + fs.unlinkSync(configFile); + }); + it('parse and returns empty config if there is no RENOVATE_CONFIG_FILE in env', async () => { expect(await file.getConfig({})).toBeDefined(); }); diff --git a/lib/workers/global/config/parse/file.ts b/lib/workers/global/config/parse/file.ts index ac56ec629a964a..61657fad2088c1 100644 --- a/lib/workers/global/config/parse/file.ts +++ b/lib/workers/global/config/parse/file.ts @@ -2,12 +2,12 @@ import is from '@sindresorhus/is'; import fs from 'fs-extra'; import JSON5 from 'json5'; import upath from 'upath'; -import { migrateConfig } from '../../../../config/migration'; import type { AllConfig, RenovateConfig } from '../../../../config/types'; import { logger } from '../../../../logger'; import { parseJson } from '../../../../util/common'; import { readSystemFile } from '../../../../util/fs'; import { parseSingleYaml } from '../../../../util/yaml'; +import { migrateAndValidateConfig } from './util'; export async function getParsedContent(file: string): Promise { if (upath.basename(file) === '.renovaterc') { @@ -81,15 +81,7 @@ export async function getConfig(env: NodeJS.ProcessEnv): Promise { await deleteNonDefaultConfig(env); // Try deletion only if RENOVATE_CONFIG_FILE is specified - const { isMigrated, migratedConfig } = migrateConfig(config); - if (isMigrated) { - logger.warn( - { originalConfig: config, migratedConfig }, - 'Config needs migrating', - ); - config = migratedConfig; - } - return config; + return migrateAndValidateConfig(config, configFile); } export async function deleteNonDefaultConfig( diff --git a/lib/workers/global/config/parse/index.ts b/lib/workers/global/config/parse/index.ts index 859ca93a9b8f29..e29b7ac379903c 100644 --- a/lib/workers/global/config/parse/index.ts +++ b/lib/workers/global/config/parse/index.ts @@ -23,7 +23,7 @@ export async function parseConfigs( const defaultConfig = defaultsParser.getConfig(); const fileConfig = await fileParser.getConfig(env); const cliConfig = cliParser.getConfig(argv); - const envConfig = envParser.getConfig(env); + const envConfig = await envParser.getConfig(env); let config: AllConfig = mergeChildConfig(fileConfig, envConfig); config = mergeChildConfig(config, cliConfig); diff --git a/lib/workers/global/config/parse/util.spec.ts b/lib/workers/global/config/parse/util.spec.ts new file mode 100644 index 00000000000000..5aadd8c0464aa7 --- /dev/null +++ b/lib/workers/global/config/parse/util.spec.ts @@ -0,0 +1,20 @@ +import { logger } from '../../../../logger'; +import { migrateAndValidateConfig } from './util'; + +describe('workers/global/config/parse/util', () => { + it('massages config', async () => { + const config = { + packageRules: [ + { + description: 'haha', + matchPackageNames: ['name'], + enabled: false, + }, + ], + }; + + const migratedConfig = await migrateAndValidateConfig(config, 'global'); + expect(migratedConfig?.packageRules?.[0].description).toBeArray(); + expect(logger.warn).toHaveBeenCalledTimes(0); + }); +}); diff --git a/lib/workers/global/config/parse/util.ts b/lib/workers/global/config/parse/util.ts new file mode 100644 index 00000000000000..9d007c8df4b4d8 --- /dev/null +++ b/lib/workers/global/config/parse/util.ts @@ -0,0 +1,38 @@ +import { dequal } from 'dequal'; +import { massageConfig } from '../../../../config/massage'; +import { migrateConfig } from '../../../../config/migration'; +import type { RenovateConfig } from '../../../../config/types'; +import { validateConfig } from '../../../../config/validation'; +import { logger } from '../../../../logger'; + +export async function migrateAndValidateConfig( + config: RenovateConfig, + configType: string, +): Promise { + const { isMigrated, migratedConfig } = migrateConfig(config); + if (isMigrated) { + logger.warn( + { originalConfig: config, migratedConfig }, + `${configType} needs migrating`, + ); + } + const massagedConfig = massageConfig(migratedConfig); + // log only if it's changed + if (!dequal(migratedConfig, massagedConfig)) { + logger.trace({ config: massagedConfig }, 'Post-massage config'); + } + + const { warnings, errors } = await validateConfig('global', massagedConfig); + + if (warnings.length) { + logger.warn( + { warnings }, + `Config validation warnings found in ${configType}`, + ); + } + if (errors.length) { + logger.warn({ errors }, `Config validation errors found in ${configType}`); + } + + return massagedConfig; +} diff --git a/lib/workers/global/index.spec.ts b/lib/workers/global/index.spec.ts index d7085312642119..a75815c0252165 100644 --- a/lib/workers/global/index.spec.ts +++ b/lib/workers/global/index.spec.ts @@ -1,6 +1,7 @@ import { ERROR, WARN } from 'bunyan'; import fs from 'fs-extra'; -import { logger, mocked } from '../../../test/util'; +import { RenovateConfig, logger, mocked } from '../../../test/util'; +import { GlobalConfig } from '../../config/global'; import * as _presets from '../../config/presets'; import { CONFIG_PRESETS_INVALID } from '../../constants/error-messages'; import { DockerDatasource } from '../../modules/datasource/docker'; @@ -40,10 +41,39 @@ const initPlatform = jest.spyOn(platform, 'initPlatform'); describe('workers/global/index', () => { beforeEach(() => { - logger.getProblems.mockImplementationOnce(() => []); + logger.getProblems.mockImplementation(() => []); initPlatform.mockImplementation((input) => Promise.resolve(input)); delete process.env.AWS_SECRET_ACCESS_KEY; delete process.env.AWS_SESSION_TOKEN; + delete process.env.RENOVATE_X_EAGER_GLOBAL_EXTENDS; + }); + + describe('getRepositoryConfig', () => { + const globalConfig: RenovateConfig = { baseDir: '/tmp/base' }; + + GlobalConfig.set({ platform: 'gitlab' }); + + it('should generate correct topLevelOrg/parentOrg with multiple levels', async () => { + const repository = 'a/b/c/d'; + const repoConfig = await globalWorker.getRepositoryConfig( + globalConfig, + repository, + ); + expect(repoConfig.topLevelOrg).toBe('a'); + expect(repoConfig.parentOrg).toBe('a/b/c'); + expect(repoConfig.repository).toBe('a/b/c/d'); + }); + + it('should generate correct topLevelOrg/parentOrg with two levels', async () => { + const repository = 'a/b'; + const repoConfig = await globalWorker.getRepositoryConfig( + globalConfig, + repository, + ); + expect(repoConfig.topLevelOrg).toBe('a'); + expect(repoConfig.parentOrg).toBe('a'); + expect(repoConfig.repository).toBe('a/b'); + }); }); it('handles config warnings and errors', async () => { @@ -58,6 +88,21 @@ describe('workers/global/index', () => { expect(addSecretForSanitizing).toHaveBeenCalledTimes(2); }); + it('resolves global presets first', async () => { + process.env.RENOVATE_X_EAGER_GLOBAL_EXTENDS = 'true'; + parseConfigs.mockResolvedValueOnce({ + repositories: [], + globalExtends: [':pinVersions'], + hostRules: [{ matchHost: 'github.com', token: 'abc123' }], + }); + presets.resolveConfigPresets.mockResolvedValueOnce({}); + await expect(globalWorker.start()).resolves.toBe(0); + expect(presets.resolveConfigPresets).toHaveBeenCalledWith({ + extends: [':pinVersions'], + }); + expect(parseConfigs).toHaveBeenCalledTimes(1); + }); + it('resolves global presets immediately', async () => { parseConfigs.mockResolvedValueOnce({ repositories: [], @@ -149,7 +194,7 @@ describe('workers/global/index', () => { repositories: [], }); logger.getProblems.mockReset(); - logger.getProblems.mockImplementationOnce(() => [ + logger.getProblems.mockImplementation(() => [ { level: ERROR, msg: 'meh', @@ -166,7 +211,7 @@ describe('workers/global/index', () => { repositories: [], }); logger.getProblems.mockReset(); - logger.getProblems.mockImplementationOnce(() => [ + logger.getProblems.mockImplementation(() => [ { level: WARN, msg: 'meh', diff --git a/lib/workers/global/index.ts b/lib/workers/global/index.ts index cd285c06ee7693..9a53fdce8a31d3 100644 --- a/lib/workers/global/index.ts +++ b/lib/workers/global/index.ts @@ -16,6 +16,7 @@ import type { import { CONFIG_PRESETS_INVALID } from '../../constants/error-messages'; import { pkg } from '../../expose.cjs'; import { instrument } from '../../instrumentation'; +import { exportStats, finalizeReport } from '../../instrumentation/reporting'; import { getProblems, logger, setMeta } from '../../logger'; import { setGlobalLogLevelRemaps } from '../../logger/remap'; import * as hostRules from '../../util/host-rules'; @@ -37,6 +38,10 @@ export async function getRepositoryConfig( globalConfig, is.string(repository) ? { repository } : repository, ); + const repoParts = repoConfig.repository.split('/'); + repoParts.pop(); + repoConfig.parentOrg = repoParts.join('/'); + repoConfig.topLevelOrg = repoParts.shift(); // TODO: types (#22198) const platform = GlobalConfig.get('platform')!; repoConfig.localDir = @@ -139,11 +144,23 @@ export async function start(): Promise { config = await getGlobalConfig(); if (config?.globalExtends) { // resolve global presets immediately - config = mergeChildConfig( - config, - await resolveGlobalExtends(config.globalExtends), - ); + if (process.env.RENOVATE_X_EAGER_GLOBAL_EXTENDS) { + config = mergeChildConfig( + await resolveGlobalExtends(config.globalExtends), + config, + ); + } else { + config = mergeChildConfig( + config, + await resolveGlobalExtends(config.globalExtends), + ); + } } + + // Set allowedHeaders in case hostRules headers are configured in file config + GlobalConfig.set({ + allowedHeaders: config.allowedHeaders, + }); // initialize all submodules config = await globalInitialize(config); @@ -210,6 +227,9 @@ export async function start(): Promise { }, ); } + + finalizeReport(); + await exportStats(config); } catch (err) /* istanbul ignore next */ { if (err.message.startsWith('Init: ')) { logger.fatal(err.message.substring(6)); diff --git a/lib/workers/repository/config-migration/index.spec.ts b/lib/workers/repository/config-migration/index.spec.ts index 9f107189397a1a..3067573bedbeb8 100644 --- a/lib/workers/repository/config-migration/index.spec.ts +++ b/lib/workers/repository/config-migration/index.spec.ts @@ -28,6 +28,13 @@ describe('workers/repository/config-migration/index', () => { }); }); + it('does nothing when in silent mode', async () => { + await configMigration({ ...config, mode: 'silent' }, []); + expect(MigratedDataFactory.getAsync).toHaveBeenCalledTimes(0); + expect(checkConfigMigrationBranch).toHaveBeenCalledTimes(0); + expect(ensureConfigMigrationPr).toHaveBeenCalledTimes(0); + }); + it('does nothing when config migration is disabled', async () => { await configMigration({ ...config, configMigration: false }, []); expect(MigratedDataFactory.getAsync).toHaveBeenCalledTimes(0); diff --git a/lib/workers/repository/config-migration/index.ts b/lib/workers/repository/config-migration/index.ts index 0746ccdf8d77be..856b0ceb277268 100644 --- a/lib/workers/repository/config-migration/index.ts +++ b/lib/workers/repository/config-migration/index.ts @@ -1,4 +1,5 @@ import type { RenovateConfig } from '../../../config/types'; +import { logger } from '../../../logger'; import { checkConfigMigrationBranch } from './branch'; import { MigratedDataFactory } from './branch/migrated-data'; import { ensureConfigMigrationPr } from './pr'; @@ -8,6 +9,12 @@ export async function configMigration( branchList: string[], ): Promise { if (config.configMigration) { + if (config.mode === 'silent') { + logger.debug( + 'Config migration issues are not created, updated or closed when mode=silent', + ); + return; + } const migratedConfigData = await MigratedDataFactory.getAsync(); const migrationBranch = await checkConfigMigrationBranch( config, diff --git a/lib/workers/repository/config-migration/pr/index.spec.ts b/lib/workers/repository/config-migration/pr/index.spec.ts index 7792174a60a70f..f104d009750d7d 100644 --- a/lib/workers/repository/config-migration/pr/index.spec.ts +++ b/lib/workers/repository/config-migration/pr/index.spec.ts @@ -144,8 +144,8 @@ describe('workers/repository/config-migration/pr/index', () => { ); expect(platform.createPr).toHaveBeenCalledTimes(1); expect(platform.createPr.mock.calls[0][0].labels).toEqual([ - 'label', 'additional-label', + 'label', ]); }); diff --git a/lib/workers/repository/dependency-dashboard.spec.ts b/lib/workers/repository/dependency-dashboard.spec.ts index 8b12cbfba9e73d..54be5280260931 100644 --- a/lib/workers/repository/dependency-dashboard.spec.ts +++ b/lib/workers/repository/dependency-dashboard.spec.ts @@ -241,6 +241,17 @@ describe('workers/repository/dependency-dashboard', () => { logger.getProblems.mockReturnValue([]); }); + it('does nothing if mode=silent', async () => { + const branches: BranchConfig[] = []; + config.mode = 'silent'; + await dependencyDashboard.ensureDependencyDashboard(config, branches); + expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(0); + expect(platform.ensureIssue).toHaveBeenCalledTimes(0); + + // same with dry run + await dryRun(branches, platform, 0, 0); + }); + it('do nothing if dependencyDashboard is disabled', async () => { const branches: BranchConfig[] = []; await dependencyDashboard.ensureDependencyDashboard(config, branches); diff --git a/lib/workers/repository/dependency-dashboard.ts b/lib/workers/repository/dependency-dashboard.ts index 92a9bbf6dc4aca..17d895c35b22ad 100644 --- a/lib/workers/repository/dependency-dashboard.ts +++ b/lib/workers/repository/dependency-dashboard.ts @@ -181,6 +181,12 @@ export async function ensureDependencyDashboard( packageFiles: Record = {}, ): Promise { logger.debug('ensureDependencyDashboard()'); + if (config.mode === 'silent') { + logger.debug( + 'Dependency Dashboard issue is not created, updated or closed when mode=silent', + ); + return; + } // legacy/migrated issue const reuseTitle = 'Update Dependencies (Renovate Bot)'; const branches = allBranches.filter( diff --git a/lib/workers/repository/error-config.spec.ts b/lib/workers/repository/error-config.spec.ts index f08d0fad23d6e3..f3dc7fb6f05dfd 100644 --- a/lib/workers/repository/error-config.spec.ts +++ b/lib/workers/repository/error-config.spec.ts @@ -29,6 +29,20 @@ describe('workers/repository/error-config', () => { GlobalConfig.reset(); }); + it('returns if mode is silent', async () => { + config.mode = 'silent'; + + const res = await raiseConfigWarningIssue( + config, + new Error(CONFIG_VALIDATION), + ); + + expect(res).toBeUndefined(); + expect(logger.debug).toHaveBeenCalledWith( + 'Config warning issues are not created, updated or closed when mode=silent', + ); + }); + it('creates issues', async () => { const expectedBody = `There are missing credentials for the authentication-required feature. As a precaution, Renovate will pause PRs until it is resolved. diff --git a/lib/workers/repository/error-config.ts b/lib/workers/repository/error-config.ts index 3654ef90db65bb..fb5c735bde65a4 100644 --- a/lib/workers/repository/error-config.ts +++ b/lib/workers/repository/error-config.ts @@ -33,6 +33,12 @@ async function raiseWarningIssue( initialBody: string, error: Error, ): Promise { + if (config.mode === 'silent') { + logger.debug( + `Config warning issues are not created, updated or closed when mode=silent`, + ); + return; + } let body = initialBody; if (error.validationSource) { body += `Location: \`${error.validationSource}\`\n`; diff --git a/lib/workers/repository/error.spec.ts b/lib/workers/repository/error.spec.ts index 6b2ed7a118299c..021836c21c7836 100644 --- a/lib/workers/repository/error.spec.ts +++ b/lib/workers/repository/error.spec.ts @@ -18,6 +18,8 @@ import { REPOSITORY_DISABLED, REPOSITORY_EMPTY, REPOSITORY_FORKED, + REPOSITORY_FORK_MISSING, + REPOSITORY_FORK_MODE_FORKED, REPOSITORY_MIRRORED, REPOSITORY_NOT_FOUND, REPOSITORY_NO_PACKAGE_FILES, @@ -47,6 +49,8 @@ describe('workers/repository/error', () => { REPOSITORY_DISABLED, REPOSITORY_CHANGED, REPOSITORY_FORKED, + REPOSITORY_FORK_MISSING, + REPOSITORY_FORK_MODE_FORKED, REPOSITORY_NO_PACKAGE_FILES, CONFIG_SECRETS_EXPOSED, CONFIG_VALIDATION, diff --git a/lib/workers/repository/error.ts b/lib/workers/repository/error.ts index 9433458deb14fc..04a75159504a4c 100644 --- a/lib/workers/repository/error.ts +++ b/lib/workers/repository/error.ts @@ -21,6 +21,8 @@ import { REPOSITORY_DISABLED_BY_CONFIG, REPOSITORY_EMPTY, REPOSITORY_FORKED, + REPOSITORY_FORK_MISSING, + REPOSITORY_FORK_MODE_FORKED, REPOSITORY_MIRRORED, REPOSITORY_NOT_FOUND, REPOSITORY_NO_CONFIG, @@ -93,6 +95,12 @@ export default async function handleError( logger.error('Repository is not found'); return err.message; } + if (err.message === REPOSITORY_FORK_MODE_FORKED) { + logger.info( + 'Repository is a fork and cannot be processed when Renovate is running in fork mode itself', + ); + return err.message; + } if (err.message === REPOSITORY_FORKED) { logger.info( 'Repository is a fork and not manually configured - skipping - did you want to run with --fork-processing=enabled?', @@ -103,6 +111,10 @@ export default async function handleError( logger.info('Cannot fork repository - skipping'); return err.message; } + if (err.message === REPOSITORY_FORK_MISSING) { + logger.info('Cannot find fork required for fork mode - skipping'); + return err.message; + } if (err.message === REPOSITORY_NO_PACKAGE_FILES) { logger.info('Repository has no package files - skipping'); return err.message; diff --git a/lib/workers/repository/errors-warnings.spec.ts b/lib/workers/repository/errors-warnings.spec.ts index e93d5564400098..4fee6525739f7e 100644 --- a/lib/workers/repository/errors-warnings.spec.ts +++ b/lib/workers/repository/errors-warnings.spec.ts @@ -85,7 +85,7 @@ describe('workers/repository/errors-warnings', () => { " --- - > ⚠ **Warning** + > ⚠️ **Warning** > > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. @@ -133,7 +133,7 @@ describe('workers/repository/errors-warnings', () => { " --- - > ⚠ **Warning** + > ⚠️ **Warning** > > Some dependencies could not be looked up. Check the warning logs for more information. @@ -197,7 +197,7 @@ describe('workers/repository/errors-warnings', () => { " --- - > ⚠ **Warning** + > ⚠️ **Warning** > > Renovate failed to look up the following dependencies: \`dependency-1\`, \`dependency-2\`. > @@ -305,7 +305,7 @@ describe('workers/repository/errors-warnings', () => { " --- > - > ⚠ **Warning** + > ⚠️ **Warning** > > Please correct - or verify that you can safely ignore - these dependency lookup failures before you merge this PR. > diff --git a/lib/workers/repository/extract/manager-files.spec.ts b/lib/workers/repository/extract/manager-files.spec.ts index 58d074b7898ae7..0b22e14973f5c9 100644 --- a/lib/workers/repository/extract/manager-files.spec.ts +++ b/lib/workers/repository/extract/manager-files.spec.ts @@ -52,13 +52,13 @@ describe('workers/repository/extract/manager-files', () => { fileMatch.getMatchingFiles.mockReturnValue(['Dockerfile']); fs.readLocalFile.mockResolvedValueOnce('some content'); html.extractPackageFile = jest.fn(() => ({ - deps: [{}, { replaceString: 'abc' }], + deps: [{}, { replaceString: 'abc', packageName: 'p' }], })) as never; const res = await getManagerPackageFiles(managerConfig); expect(res).toEqual([ { packageFile: 'Dockerfile', - deps: [{}, { replaceString: 'abc' }], + deps: [{}, { replaceString: 'abc', packageName: 'p', depName: 'p' }], }, ]); }); diff --git a/lib/workers/repository/extract/manager-files.ts b/lib/workers/repository/extract/manager-files.ts index d247968c8ce72b..abfeaa39a2fe6f 100644 --- a/lib/workers/repository/extract/manager-files.ts +++ b/lib/workers/repository/extract/manager-files.ts @@ -9,6 +9,18 @@ import type { PackageFile } from '../../../modules/manager/types'; import { readLocalFile } from '../../../util/fs'; import type { WorkerExtractConfig } from '../../types'; +function massageDepNames(packageFiles: PackageFile[] | null): void { + if (packageFiles) { + for (const packageFile of packageFiles) { + for (const dep of packageFile.deps) { + if (dep.packageName && !dep.depName) { + dep.depName = dep.packageName; + } + } + } + } +} + export async function getManagerPackageFiles( config: WorkerExtractConfig, ): Promise { @@ -35,6 +47,7 @@ export async function getManagerPackageFiles( config, fileList, ); + massageDepNames(allPackageFiles); return allPackageFiles; } const packageFiles: PackageFile[] = []; @@ -58,5 +71,6 @@ export async function getManagerPackageFiles( logger.debug(`${packageFile} has no content`); } } + massageDepNames(packageFiles); return packageFiles; } diff --git a/lib/workers/repository/finalize/repository-statistics.ts b/lib/workers/repository/finalize/repository-statistics.ts index 830dafea93c422..36d450c92950dd 100644 --- a/lib/workers/repository/finalize/repository-statistics.ts +++ b/lib/workers/repository/finalize/repository-statistics.ts @@ -1,4 +1,5 @@ import type { RenovateConfig } from '../../../config/types'; +import { addBranchStats } from '../../../instrumentation/reporting'; import { logger } from '../../../logger'; import type { Pr } from '../../../modules/platform'; import { getCache, isCacheModified } from '../../../util/cache/repository'; @@ -79,8 +80,10 @@ function filterDependencyDashboardData( fixedVersion, currentVersion, currentValue, + currentDigest, newValue, newVersion, + newDigest, packageFile, updateType, packageName, @@ -93,8 +96,10 @@ function filterDependencyDashboardData( fixedVersion, currentVersion, currentValue, + currentDigest, newValue, newVersion, + newDigest, packageFile, updateType, packageName, @@ -148,6 +153,7 @@ export function runBranchSummary(config: RenovateConfig): void { if (branches?.length) { const branchesInformation = filterDependencyDashboardData(branches); + addBranchStats(config, branchesInformation); logger.debug({ branchesInformation }, 'branches info extended'); } } diff --git a/lib/workers/repository/index.ts b/lib/workers/repository/index.ts index fa2294a0203b5d..f9d30bcf787aef 100644 --- a/lib/workers/repository/index.ts +++ b/lib/workers/repository/index.ts @@ -9,6 +9,7 @@ import { } from '../../constants/error-messages'; import { pkg } from '../../expose.cjs'; import { instrument } from '../../instrumentation'; +import { addExtractionStats } from '../../instrumentation/reporting'; import { logger, setMeta } from '../../logger'; import { resetRepositoryLogLevelRemaps } from '../../logger/remap'; import { removeDanglingContainers } from '../../util/exec/docker'; @@ -19,6 +20,12 @@ import { clearDnsCache, printDnsStats } from '../../util/http/dns'; import * as queue from '../../util/http/queue'; import * as throttle from '../../util/http/throttle'; import { addSplit, getSplits, splitInit } from '../../util/split'; +import { + HttpCacheStats, + HttpStats, + LookupStats, + PackageCacheStats, +} from '../../util/stats'; import { setBranchCache } from './cache'; import { extractRepoProblems } from './common'; import { ensureDependencyDashboard } from './dependency-dashboard'; @@ -31,7 +38,6 @@ import { ensureOnboardingPr } from './onboarding/pr'; import { extractDependencies, updateRepo } from './process'; import type { ExtractResult } from './process/extract-update'; import { ProcessResult, processResult } from './result'; -import { printLookupStats, printRequestStats } from './stats'; // istanbul ignore next export async function renovateRepository( @@ -59,9 +65,13 @@ export async function renovateRepository( config.repoIsOnboarded! || !OnboardingState.onboardingCacheValid || OnboardingState.prUpdateRequested; - const { branches, branchList, packageFiles } = performExtract + const extractResult = performExtract ? await instrument('extract', () => extractDependencies(config)) : emptyExtract(config); + addExtractionStats(config, extractResult); + + const { branches, branchList, packageFiles } = extractResult; + if (config.semanticCommits === 'auto') { config.semanticCommits = await detectSemanticCommits(); } @@ -124,8 +134,10 @@ export async function renovateRepository( } const splits = getSplits(); logger.debug(splits, 'Repository timing splits (milliseconds)'); - printRequestStats(); - printLookupStats(); + PackageCacheStats.report(); + HttpStats.report(); + HttpCacheStats.report(); + LookupStats.report(); printDnsStats(); clearDnsCache(); const cloned = isCloned(); diff --git a/lib/workers/repository/init/cache.ts b/lib/workers/repository/init/cache.ts index c255541aa6e4c8..d1631fbc052b5a 100644 --- a/lib/workers/repository/init/cache.ts +++ b/lib/workers/repository/init/cache.ts @@ -15,7 +15,6 @@ export async function resetCaches(): Promise { export async function initializeCaches( config: WorkerPlatformConfig, ): Promise { - memCache.init(); await initRepoCache(config); await fs.ensureDir(privateCacheDir()); npmApi.setNpmrc(); diff --git a/lib/workers/repository/init/config.ts b/lib/workers/repository/init/config.ts index 96f64a7ba73913..a8f9a14c6dfece 100644 --- a/lib/workers/repository/init/config.ts +++ b/lib/workers/repository/init/config.ts @@ -1,5 +1,6 @@ import type { RenovateConfig } from '../../../config/types'; import { checkOnboardingBranch } from '../onboarding/branch'; +import { mergeInheritedConfig } from './inherited'; import { mergeRenovateConfig } from './merge'; // istanbul ignore next @@ -8,6 +9,7 @@ export async function getRepoConfig( ): Promise { let config = { ...config_ }; config.baseBranch = config.defaultBranch; + config = await mergeInheritedConfig(config); config = await checkOnboardingBranch(config); config = await mergeRenovateConfig(config); return config; diff --git a/lib/workers/repository/init/index.spec.ts b/lib/workers/repository/init/index.spec.ts index 1927900bcd683f..4f96f018118905 100644 --- a/lib/workers/repository/init/index.spec.ts +++ b/lib/workers/repository/init/index.spec.ts @@ -38,7 +38,7 @@ describe('workers/repository/init/index', () => { it('runs', async () => { apis.initApis.mockResolvedValue(partial<_apis.WorkerPlatformConfig>()); onboarding.checkOnboardingBranch.mockResolvedValueOnce({}); - config.getRepoConfig.mockResolvedValueOnce({}); + config.getRepoConfig.mockResolvedValueOnce({ mode: 'silent' }); merge.mergeRenovateConfig.mockResolvedValueOnce({}); secrets.applySecretsToConfig.mockReturnValueOnce( partial(), diff --git a/lib/workers/repository/init/index.ts b/lib/workers/repository/init/index.ts index 81f05826c03f10..8f21f936db2e40 100644 --- a/lib/workers/repository/init/index.ts +++ b/lib/workers/repository/init/index.ts @@ -4,6 +4,7 @@ import type { RenovateConfig } from '../../../config/types'; import { logger } from '../../../logger'; import { setRepositoryLogLevelRemaps } from '../../../logger/remap'; import { platform } from '../../../modules/platform'; +import * as memCache from '../../../util/cache/memory'; import { clone } from '../../../util/clone'; import { cloneSubmodules, setUserRepoConfig } from '../../../util/git'; import { getAll } from '../../../util/host-rules'; @@ -48,10 +49,16 @@ export async function initRepo( let config: RenovateConfig = initializeConfig(config_); await resetCaches(); logger.once.reset(); + memCache.init(); config = await initApis(config); await initializeCaches(config as WorkerPlatformConfig); config = await getRepoConfig(config); setRepositoryLogLevelRemaps(config.logLevelRemap); + if (config.mode === 'silent') { + logger.info( + 'Repository is running with mode=silent and will not make Issues or PRs by default', + ); + } checkIfConfigured(config); warnOnUnsupportedOptions(config); config = applySecretsToConfig(config); diff --git a/lib/workers/repository/init/inherited.spec.ts b/lib/workers/repository/init/inherited.spec.ts new file mode 100644 index 00000000000000..f89c18201649c8 --- /dev/null +++ b/lib/workers/repository/init/inherited.spec.ts @@ -0,0 +1,87 @@ +import { platform } from '../../../../test/util'; +import type { RenovateConfig } from '../../../config/types'; +import { + CONFIG_INHERIT_NOT_FOUND, + CONFIG_INHERIT_PARSE_ERROR, + CONFIG_VALIDATION, +} from '../../../constants/error-messages'; +import { logger } from '../../../logger'; +import { mergeInheritedConfig } from './inherited'; + +describe('workers/repository/init/inherited', () => { + let config: RenovateConfig; + + beforeEach(() => { + config = { + repository: 'test/repo', + inheritConfig: true, + inheritConfigRepoName: 'inherit/repo', + inheritConfigFileName: 'config.json', + inheritConfigStrict: false, + }; + }); + + it('should return the same config if repository or inheritConfig is not defined', async () => { + config.repository = undefined; + const result = await mergeInheritedConfig(config); + expect(result).toEqual(config); + }); + + it('should return the same config if inheritConfigRepoName or inheritConfigFileName is not a string', async () => { + config.inheritConfigRepoName = undefined; + const result = await mergeInheritedConfig(config); + expect(result).toEqual(config); + }); + + it('should throw an error if getting the raw file fails and inheritConfigStrict is true', async () => { + config.inheritConfigStrict = true; + platform.getRawFile.mockRejectedValue(new Error('File not found')); + await expect(mergeInheritedConfig(config)).rejects.toThrow( + CONFIG_INHERIT_NOT_FOUND, + ); + }); + + it('should return the same config if getting the raw file fails and inheritConfigStrict is false', async () => { + platform.getRawFile.mockRejectedValue(new Error('File not found')); + const result = await mergeInheritedConfig(config); + expect(result).toEqual(config); + }); + + it('should throw an error if parsing the inherited config fails', async () => { + platform.getRawFile.mockResolvedValue('invalid json'); + await expect(mergeInheritedConfig(config)).rejects.toThrow( + CONFIG_INHERIT_PARSE_ERROR, + ); + }); + + it('should throw an error if config includes an invalid option', async () => { + platform.getRawFile.mockResolvedValue('{"something": "invalid"}'); + await expect(mergeInheritedConfig(config)).rejects.toThrow( + CONFIG_VALIDATION, + ); + }); + + it('should throw an error if config includes an invalid value', async () => { + platform.getRawFile.mockResolvedValue('{"onboarding": "invalid"}'); + await expect(mergeInheritedConfig(config)).rejects.toThrow( + CONFIG_VALIDATION, + ); + }); + + it('should warn if validateConfig returns warnings', async () => { + platform.getRawFile.mockResolvedValue('{"binarySource": "docker"}'); + const res = await mergeInheritedConfig(config); + expect(res.binarySource).toBeUndefined(); + expect(logger.warn).toHaveBeenCalled(); + }); + + it('should merge inherited config', async () => { + platform.getRawFile.mockResolvedValue( + '{"onboarding":false,"labels":["test"]}', + ); + const res = await mergeInheritedConfig(config); + expect(res.labels).toEqual(['test']); + expect(res.onboarding).toBeFalse(); + expect(logger.warn).not.toHaveBeenCalled(); + }); +}); diff --git a/lib/workers/repository/init/inherited.ts b/lib/workers/repository/init/inherited.ts new file mode 100644 index 00000000000000..f25be4f3f3169e --- /dev/null +++ b/lib/workers/repository/init/inherited.ts @@ -0,0 +1,103 @@ +import is from '@sindresorhus/is'; +import { dequal } from 'dequal'; +import { mergeChildConfig, removeGlobalConfig } from '../../../config'; +import { parseFileConfig } from '../../../config/parse'; +import type { RenovateConfig } from '../../../config/types'; +import { validateConfig } from '../../../config/validation'; +import { + CONFIG_INHERIT_NOT_FOUND, + CONFIG_INHERIT_PARSE_ERROR, + CONFIG_VALIDATION, +} from '../../../constants/error-messages'; +import { logger } from '../../../logger'; +import { platform } from '../../../modules/platform'; +import * as template from '../../../util/template'; + +export async function mergeInheritedConfig( + config: RenovateConfig, +): Promise { + // typescript doesn't know that repo is defined + if (!config.repository || !config.inheritConfig) { + return config; + } + if ( + !is.string(config.inheritConfigRepoName) || + !is.string(config.inheritConfigFileName) + ) { + // Config validation should prevent this error + logger.error( + { + inheritConfigRepoName: config.inheritConfigRepoName, + inheritConfigFileName: config.inheritConfigFileName, + }, + 'Invalid inherited config.', + ); + return config; + } + const templateConfig = { + topLevelOrg: config.topLevelOrg, + parentOrg: config.parentOrg, + repository: config.repository, + }; + const inheritConfigRepoName = template.compile( + config.inheritConfigRepoName, + templateConfig, + false, + ); + logger.trace( + { templateConfig, inheritConfigRepoName }, + 'Compiled inheritConfigRepoName result.', + ); + logger.debug( + `Checking for inherited config file ${config.inheritConfigFileName} in repo ${inheritConfigRepoName}.`, + ); + let configFileRaw: string | null = null; + try { + configFileRaw = await platform.getRawFile( + config.inheritConfigFileName, + inheritConfigRepoName, + ); + } catch (err) { + if (config.inheritConfigStrict) { + logger.debug({ err }, 'Error getting inherited config.'); + throw new Error(CONFIG_INHERIT_NOT_FOUND); + } + logger.trace({ err }, `Error getting inherited config.`); + } + if (!configFileRaw) { + logger.debug(`No inherited config found in ${inheritConfigRepoName}.`); + return config; + } + const parseResult = parseFileConfig( + config.inheritConfigFileName, + configFileRaw, + ); + if (!parseResult.success) { + logger.debug({ parseResult }, 'Error parsing inherited config.'); + throw new Error(CONFIG_INHERIT_PARSE_ERROR); + } + const inheritedConfig = parseResult.parsedContents as RenovateConfig; + logger.debug({ config: inheritedConfig }, `Inherited config`); + const res = await validateConfig('inherit', inheritedConfig); + if (res.errors.length) { + logger.warn( + { errors: res.errors }, + 'Found errors in inherited configuration.', + ); + throw new Error(CONFIG_VALIDATION); + } + if (res.warnings.length) { + logger.warn( + { warnings: res.warnings }, + 'Found warnings in inherited configuration.', + ); + } + const filteredConfig = removeGlobalConfig(inheritedConfig, true); + if (!dequal(inheritedConfig, filteredConfig)) { + logger.debug( + { inheritedConfig, filteredConfig }, + 'Removed global config from inherited config.', + ); + } + return mergeChildConfig(config, filteredConfig); +} diff --git a/lib/workers/repository/init/merge.spec.ts b/lib/workers/repository/init/merge.spec.ts index 51fb01fe249936..7cc7a92a057cec 100644 --- a/lib/workers/repository/init/merge.spec.ts +++ b/lib/workers/repository/init/merge.spec.ts @@ -280,6 +280,18 @@ describe('workers/repository/init/merge', () => { }); }); + it('uses onboarding config if silent', async () => { + scm.getFileList.mockResolvedValue([]); + migrateAndValidate.migrateAndValidate.mockResolvedValue({ + warnings: [], + errors: [], + }); + config.mode = 'silent'; + config.repository = 'some-org/some-repo'; + const res = await mergeRenovateConfig(config); + expect(res).toBeDefined(); + }); + it('throws error if misconfigured', async () => { scm.getFileList.mockResolvedValue(['package.json', '.renovaterc.json']); fs.readLocalFile.mockResolvedValue('{}'); diff --git a/lib/workers/repository/init/merge.ts b/lib/workers/repository/init/merge.ts index f5acc402f63de3..dff41b852fafe7 100644 --- a/lib/workers/repository/init/merge.ts +++ b/lib/workers/repository/init/merge.ts @@ -1,12 +1,10 @@ import is from '@sindresorhus/is'; -import jsonValidator from 'json-dup-key-validator'; -import JSON5 from 'json5'; -import upath from 'upath'; import { mergeChildConfig } from '../../../config'; import { configFileNames } from '../../../config/app-strings'; import { decryptConfig } from '../../../config/decrypt'; import { migrateAndValidate } from '../../../config/migrate-validate'; import { migrateConfig } from '../../../config/migration'; +import { parseFileConfig } from '../../../config/parse'; import * as presets from '../../../config/presets'; import { applySecretsToConfig } from '../../../config/secrets'; import type { RenovateConfig } from '../../../config/types'; @@ -25,6 +23,8 @@ import { readLocalFile } from '../../../util/fs'; import * as hostRules from '../../../util/host-rules'; import * as queue from '../../../util/http/queue'; import * as throttle from '../../../util/http/throttle'; +import { getOnboardingConfig } from '../onboarding/branch/config'; +import { getDefaultConfigFileName } from '../onboarding/branch/create'; import { getOnboardingConfigFromCache, getOnboardingFileNameFromCache, @@ -60,7 +60,7 @@ export async function detectConfigFile(): Promise { export async function detectRepoFileConfig(): Promise { const cache = getCache(); let { configFileName } = cache; - if (configFileName) { + if (is.nonEmptyString(configFileName)) { let configFileRaw: string | null; try { configFileRaw = await platform.getRawFile(configFileName); @@ -91,6 +91,7 @@ export async function detectRepoFileConfig(): Promise { if (!configFileName) { logger.debug('No renovate config file found'); + cache.configFileName = ''; return {}; } cache.configFileName = configFileName; @@ -131,71 +132,18 @@ export async function detectRepoFileConfig(): Promise { configFileRaw = '{}'; } - const fileType = upath.extname(configFileName); + const parseResult = parseFileConfig(configFileName, configFileRaw); - if (fileType === '.json5') { - try { - configFileParsed = JSON5.parse(configFileRaw); - } catch (err) /* istanbul ignore next */ { - logger.debug( - { renovateConfig: configFileRaw }, - 'Error parsing renovate config renovate.json5', - ); - const validationError = 'Invalid JSON5 (parsing failed)'; - const validationMessage = `JSON5.parse error: \`${err.message.replaceAll( - '`', - "'", - )}\``; - return { - configFileName, - configFileParseError: { validationError, validationMessage }, - }; - } - } else { - let allowDuplicateKeys = true; - let jsonValidationError = jsonValidator.validate( - configFileRaw, - allowDuplicateKeys, - ); - if (jsonValidationError) { - const validationError = 'Invalid JSON (parsing failed)'; - const validationMessage = jsonValidationError; - return { - configFileName, - configFileParseError: { validationError, validationMessage }, - }; - } - allowDuplicateKeys = false; - jsonValidationError = jsonValidator.validate( - configFileRaw, - allowDuplicateKeys, - ); - if (jsonValidationError) { - const validationError = 'Duplicate keys in JSON'; - const validationMessage = JSON.stringify(jsonValidationError); - return { - configFileName, - configFileParseError: { validationError, validationMessage }, - }; - } - try { - configFileParsed = parseJson(configFileRaw, configFileName); - } catch (err) /* istanbul ignore next */ { - logger.debug( - { renovateConfig: configFileRaw }, - 'Error parsing renovate config', - ); - const validationError = 'Invalid JSON (parsing failed)'; - const validationMessage = `JSON.parse error: \`${err.message.replaceAll( - '`', - "'", - )}\``; - return { - configFileName, - configFileParseError: { validationError, validationMessage }, - }; - } + if (!parseResult.success) { + return { + configFileName, + configFileParseError: { + validationError: parseResult.validationError, + validationMessage: parseResult.validationMessage, + }, + }; } + configFileParsed = parseResult.parsedContents; logger.debug( { fileName: configFileName, config: configFileParsed }, 'Repository config', @@ -226,6 +174,16 @@ export async function mergeRenovateConfig( if (config.requireConfig !== 'ignored') { repoConfig = await detectRepoFileConfig(); } + if (!repoConfig.configFileParsed && config.mode === 'silent') { + logger.debug( + 'When mode=silent and repo has no config file, we use the onboarding config as repo config', + ); + const configFileName = getDefaultConfigFileName(config); + repoConfig = { + configFileName, + configFileParsed: await getOnboardingConfig(config), + }; + } const configFileParsed = repoConfig?.configFileParsed || {}; if (is.nonEmptyArray(returnConfig.extends)) { configFileParsed.extends = [ diff --git a/lib/workers/repository/init/vulnerability.spec.ts b/lib/workers/repository/init/vulnerability.spec.ts index c5e375bfceec33..db6ca1ca50bf87 100644 --- a/lib/workers/repository/init/vulnerability.spec.ts +++ b/lib/workers/repository/init/vulnerability.spec.ts @@ -368,6 +368,41 @@ describe('workers/repository/init/vulnerability', () => { expect(res.packageRules).toHaveLength(1); }); + it('returns pip alerts with normalized name', async () => { + // TODO #22198 + delete config.vulnerabilityAlerts!.enabled; + platform.getVulnerabilityAlerts.mockResolvedValue([ + { + dismissReason: null, + vulnerableManifestFilename: 'requirements.txt', + vulnerableManifestPath: 'requirements.txt', + vulnerableRequirements: '= 1.6.7', + securityAdvisory: { + description: 'Description', + identifiers: [ + { type: 'GHSA', value: 'GHSA-m956-frf4-m2wr' }, + { type: 'CVE', value: 'CVE-2016-2137' }, + ], + references: [ + { url: 'https://nvd.nist.gov/vuln/detail/CVE-2016-9587' }, + ], + severity: 'MODERATE', + }, + securityVulnerability: { + package: { name: 'Pillow', ecosystem: 'PIP' }, + firstPatchedVersion: { identifier: '2.1.4' }, + vulnerableVersionRange: '< 2.1.4', + }, + }, + ]); + const res = await detectVulnerabilityAlerts(config); + expect(res.packageRules).toHaveLength(1); + expect(res.packageRules![0].matchPackageNames).toEqual([ + 'Pillow', + 'pillow', + ]); + }); + it('returns remediations', async () => { config.transitiveRemediation = true; // TODO #22198 diff --git a/lib/workers/repository/init/vulnerability.ts b/lib/workers/repository/init/vulnerability.ts index 4f1c0242dc37f7..ed3efe653f8267 100644 --- a/lib/workers/repository/init/vulnerability.ts +++ b/lib/workers/repository/init/vulnerability.ts @@ -9,6 +9,7 @@ import { NpmDatasource } from '../../../modules/datasource/npm'; import { NugetDatasource } from '../../../modules/datasource/nuget'; import { PackagistDatasource } from '../../../modules/datasource/packagist'; import { PypiDatasource } from '../../../modules/datasource/pypi'; +import { normalizePythonDepName } from '../../../modules/datasource/pypi/common'; import { RubyGemsDatasource } from '../../../modules/datasource/rubygems'; import { platform } from '../../../modules/platform'; import * as allVersioning from '../../../modules/versioning'; @@ -218,6 +219,12 @@ export async function detectVulnerabilityAlerts( matchCurrentVersion, matchFileNames, }; + if ( + datasource === PypiDatasource.id && + normalizePythonDepName(depName) !== depName + ) { + matchRule.matchPackageNames?.push(normalizePythonDepName(depName)); + } const supportedRemediationFileTypes = ['package-lock.json']; if ( config.transitiveRemediation && diff --git a/lib/workers/repository/onboarding/branch/check.spec.ts b/lib/workers/repository/onboarding/branch/check.spec.ts index d4130c9a1db812..1761e95932037c 100644 --- a/lib/workers/repository/onboarding/branch/check.spec.ts +++ b/lib/workers/repository/onboarding/branch/check.spec.ts @@ -25,6 +25,11 @@ describe('workers/repository/onboarding/branch/check', () => { onboarding: true, }); + it('returns true if in silent mode', async () => { + const res = await isOnboarded({ ...config, mode: 'silent' }); + expect(res).toBeTrue(); + }); + it('skips normal onboarding check if onboardingCache is valid', async () => { cache.getCache.mockReturnValueOnce({ onboardingBranchCache: { diff --git a/lib/workers/repository/onboarding/branch/check.ts b/lib/workers/repository/onboarding/branch/check.ts index c3773f789b9dbb..e4699ac03cb181 100644 --- a/lib/workers/repository/onboarding/branch/check.ts +++ b/lib/workers/repository/onboarding/branch/check.ts @@ -63,6 +63,12 @@ export async function isOnboarded(config: RenovateConfig): Promise { logger.debug('isOnboarded()'); const title = `Action required: Add a Renovate config`; + // Repo is onboarded if in silent mode + if (config.mode === 'silent') { + logger.debug('Silent mode enabled so repo is considered onboarded'); + return true; + } + // Repo is onboarded if global config is bypassing onboarding and does not require a // configuration file. // The repo is considered "not onboarded" if: diff --git a/lib/workers/repository/onboarding/branch/create.ts b/lib/workers/repository/onboarding/branch/create.ts index 708d82cc33f167..8070cfed2a5831 100644 --- a/lib/workers/repository/onboarding/branch/create.ts +++ b/lib/workers/repository/onboarding/branch/create.ts @@ -9,22 +9,27 @@ import { getOnboardingConfigContents } from './config'; const defaultConfigFile = configFileNames[0]; -export async function createOnboardingBranch( +export function getDefaultConfigFileName( config: Partial, -): Promise { +): string { // TODO #22198 - const configFile = configFileNames.includes(config.onboardingConfigFileName!) - ? config.onboardingConfigFileName + return configFileNames.includes(config.onboardingConfigFileName!) + ? config.onboardingConfigFileName! : defaultConfigFile; +} +export async function createOnboardingBranch( + config: Partial, +): Promise { logger.debug('createOnboardingBranch()'); + const configFile = getDefaultConfigFileName(config); // TODO #22198 - const contents = await getOnboardingConfigContents(config, configFile!); + const contents = await getOnboardingConfigContents(config, configFile); logger.debug('Creating onboarding branch'); const commitMessageFactory = new OnboardingCommitMessageFactory( config, - configFile!, + configFile, ); let commitMessage = commitMessageFactory.create().toString(); @@ -51,7 +56,7 @@ export async function createOnboardingBranch( { type: 'addition', // TODO #22198 - path: configFile!, + path: configFile, contents, }, ], diff --git a/lib/workers/repository/onboarding/pr/index.spec.ts b/lib/workers/repository/onboarding/pr/index.spec.ts index ba26fbbfaf414a..030ce3680d93de 100644 --- a/lib/workers/repository/onboarding/pr/index.spec.ts +++ b/lib/workers/repository/onboarding/pr/index.spec.ts @@ -106,8 +106,8 @@ describe('workers/repository/onboarding/pr/index', () => { ); expect(platform.createPr).toHaveBeenCalledTimes(1); expect(platform.createPr.mock.calls[0][0].labels).toEqual([ - 'label', 'additional-label', + 'label', ]); }); diff --git a/lib/workers/repository/process/deprecated.spec.ts b/lib/workers/repository/process/deprecated.spec.ts index a0981c7177202e..1f15b73f6db1fe 100644 --- a/lib/workers/repository/process/deprecated.spec.ts +++ b/lib/workers/repository/process/deprecated.spec.ts @@ -16,6 +16,15 @@ describe('workers/repository/process/deprecated', () => { await expect(raiseDeprecationWarnings(config, {})).resolves.not.toThrow(); }); + it('returns if in silent mode', async () => { + const config: RenovateConfig = { + repoIsOnboarded: true, + suppressNotifications: [], + mode: 'silent', + }; + await expect(raiseDeprecationWarnings(config, {})).resolves.not.toThrow(); + }); + it('raises deprecation warnings', async () => { const config: RenovateConfig = { repoIsOnboarded: true, diff --git a/lib/workers/repository/process/deprecated.ts b/lib/workers/repository/process/deprecated.ts index 43c699cbf43386..0a27d71b531572 100644 --- a/lib/workers/repository/process/deprecated.ts +++ b/lib/workers/repository/process/deprecated.ts @@ -15,6 +15,12 @@ export async function raiseDeprecationWarnings( if (config.suppressNotifications?.includes('deprecationWarningIssues')) { return; } + if (config.mode === 'silent') { + logger.debug( + `Deprecation warning issues are not created, updated or closed when mode=silent`, + ); + return; + } for (const [manager, files] of Object.entries(packageFiles)) { const deprecatedPackages: Record< string, diff --git a/lib/workers/repository/process/fetch.ts b/lib/workers/repository/process/fetch.ts index 2256a0012140a2..4e95f8b9ee1363 100644 --- a/lib/workers/repository/process/fetch.ts +++ b/lib/workers/repository/process/fetch.ts @@ -10,29 +10,15 @@ import type { PackageFile, } from '../../../modules/manager/types'; import { ExternalHostError } from '../../../types/errors/external-host-error'; -import * as memCache from '../../../util/cache/memory'; -import type { LookupStats } from '../../../util/cache/memory/types'; import { clone } from '../../../util/clone'; import { applyPackageRules } from '../../../util/package-rules'; import * as p from '../../../util/promises'; import { Result } from '../../../util/result'; +import { LookupStats } from '../../../util/stats'; import { PackageFiles } from '../package-files'; import { lookupUpdates } from './lookup'; import type { LookupUpdateConfig } from './lookup/types'; -async function withLookupStats( - datasource: string, - callback: () => Promise, -): Promise { - const start = Date.now(); - const result = await callback(); - const duration = Date.now() - start; - const lookups = memCache.get('lookup-stats') || []; - lookups.push({ datasource, duration }); - memCache.set('lookup-stats', lookups); - return result; -} - async function fetchDepUpdates( packageFileConfig: RenovateConfig & PackageFile, indep: PackageDependency, @@ -69,7 +55,7 @@ async function fetchDepUpdates( dep.skipReason = 'disabled'; } else { if (depConfig.datasource) { - const { val: updateResult, err } = await withLookupStats( + const { val: updateResult, err } = await LookupStats.wrap( depConfig.datasource, () => Result.wrap(lookupUpdates(depConfig as LookupUpdateConfig)).unwrap(), diff --git a/lib/workers/repository/process/lookup/bucket.ts b/lib/workers/repository/process/lookup/bucket.ts index 4cc4295673f891..b54b70ab99d8ea 100644 --- a/lib/workers/repository/process/lookup/bucket.ts +++ b/lib/workers/repository/process/lookup/bucket.ts @@ -3,6 +3,7 @@ import type { VersioningApi } from '../../../../modules/versioning/types'; export interface BucketConfig { separateMajorMinor?: boolean; separateMultipleMajor?: boolean; + separateMultipleMinor?: boolean; separateMinorPatch?: boolean; } @@ -12,8 +13,12 @@ export function getBucket( newVersion: string, versioning: VersioningApi, ): string | null { - const { separateMajorMinor, separateMultipleMajor, separateMinorPatch } = - config; + const { + separateMajorMinor, + separateMultipleMajor, + separateMultipleMinor, + separateMinorPatch, + } = config; if (!separateMajorMinor) { return 'latest'; } @@ -46,11 +51,9 @@ export function getBucket( // Check the minor update type first if (fromMinor !== toMinor) { - /* future option if (separateMultipleMinor) { return `v${toMajor}.${toMinor}`; } - */ if (separateMinorPatch) { return 'minor'; diff --git a/lib/workers/repository/process/lookup/current.ts b/lib/workers/repository/process/lookup/current.ts index 9656dd0e50e30b..cd37363c50441f 100644 --- a/lib/workers/repository/process/lookup/current.ts +++ b/lib/workers/repository/process/lookup/current.ts @@ -16,6 +16,9 @@ export function getCurrentVersion( return null; } logger.trace(`currentValue ${currentValue} is range`); + if (allVersions.includes(currentValue)) { + return currentValue; + } let useVersions = allVersions.filter((v) => versioning.matches(v, currentValue), ); diff --git a/lib/workers/repository/process/lookup/index.spec.ts b/lib/workers/repository/process/lookup/index.spec.ts index c4310a3444a489..9886a0eb0d7619 100644 --- a/lib/workers/repository/process/lookup/index.spec.ts +++ b/lib/workers/repository/process/lookup/index.spec.ts @@ -5,10 +5,12 @@ import * as httpMock from '../../../../../test/http-mock'; import { partial } from '../../../../../test/util'; import { getConfig } from '../../../../config/defaults'; import { CONFIG_VALIDATION } from '../../../../constants/error-messages'; +import { CustomDatasource } from '../../../../modules/datasource/custom'; import { DockerDatasource } from '../../../../modules/datasource/docker'; import { GitRefsDatasource } from '../../../../modules/datasource/git-refs'; import { GithubReleasesDatasource } from '../../../../modules/datasource/github-releases'; import { GithubTagsDatasource } from '../../../../modules/datasource/github-tags'; +import { GoDatasource } from '../../../../modules/datasource/go'; import { MavenDatasource } from '../../../../modules/datasource/maven'; import { NpmDatasource } from '../../../../modules/datasource/npm'; import { PackagistDatasource } from '../../../../modules/datasource/packagist'; @@ -62,6 +64,11 @@ describe('workers/repository/process/lookup/index', () => { const getMavenReleases = jest.spyOn(MavenDatasource.prototype, 'getReleases'); + const getCustomDatasourceReleases = jest.spyOn( + CustomDatasource.prototype, + 'getReleases', + ); + const getDockerDigest = jest.spyOn(DockerDatasource.prototype, 'getDigest'); beforeEach(() => { @@ -2785,6 +2792,21 @@ describe('workers/repository/process/lookup/index', () => { ]); }); + it('should upgrade to 16 minors', async () => { + config.currentValue = '1.0.0'; + config.separateMultipleMinor = true; + config.packageName = 'webpack'; + config.datasource = NpmDatasource.id; + httpMock + .scope('https://registry.npmjs.org') + .get('/webpack') + .reply(200, webpackJson); + const { updates } = await Result.wrap( + lookup.lookupUpdates(config), + ).unwrapOrThrow(); + expect(updates).toHaveLength(16); + }); + it('does not jump major unstable', async () => { config.currentValue = '^4.4.0-canary.3'; config.rangeStrategy = 'replace'; @@ -2970,7 +2992,7 @@ describe('workers/repository/process/lookup/index', () => { }); it('rejects non-fully specified in-range updates', async () => { - config.rangeStrategy = 'bump'; + config.rangeStrategy = 'update-lockfile'; config.currentValue = '1.x'; config.packageName = 'q'; config.datasource = NpmDatasource.id; @@ -3134,6 +3156,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res).toEqual({ currentVersion: '1.3.0', + currentVersionTimestamp: '2015-04-26T16:42:11.311Z', fixedVersion: '1.3.0', isSingleVersion: true, registryUrl: 'https://registry.npmjs.org', @@ -3154,13 +3177,16 @@ describe('workers/repository/process/lookup/index', () => { }); }); - it('ignores deprecated', async () => { + it('ignores deprecated when it is not the latest', async () => { config.currentValue = '1.3.0'; config.packageName = 'q2'; config.datasource = NpmDatasource.id; const returnJson = JSON.parse(JSON.stringify(qJson)); returnJson.name = 'q2'; + // mark latest minor as deprecated returnJson.versions['1.4.1'].deprecated = 'true'; + // make sure latest release isn't the one deprecated as otherwise every release is deprecated + returnJson['dist-tags'].latest = '2.0.3'; httpMock .scope('https://registry.npmjs.org') .get('/q2') @@ -3169,16 +3195,9 @@ describe('workers/repository/process/lookup/index', () => { const res = await Result.wrap( lookup.lookupUpdates(config), ).unwrapOrThrow(); - expect(res).toEqual({ currentVersion: '1.3.0', - deprecationMessage: codeBlock` - On registry \`https://registry.npmjs.org\`, the "latest" version of dependency \`q2\` has the following deprecation notice: - - \`true\` - - Marking the latest version of an npm package as deprecated results in the entire package being considered deprecated, so contact the package author you think this is a mistake. - `, + currentVersionTimestamp: '2015-04-26T16:42:11.311Z', fixedVersion: '1.3.0', isSingleVersion: true, registryUrl: 'https://registry.npmjs.org', @@ -3193,13 +3212,22 @@ describe('workers/repository/process/lookup/index', () => { releaseTimestamp: expect.any(String), updateType: 'minor', }, + { + bucket: 'major', + newMajor: 2, + newMinor: 0, + newValue: '2.0.3', + newVersion: '2.0.3', + releaseTimestamp: expect.any(String), + updateType: 'major', + }, ], versioning: 'npm', warnings: [], }); }); - it('is deprecated', async () => { + it('treats all versions as deprecated if latest is deprecated', async () => { config.currentValue = '1.3.0'; config.packageName = 'q3'; config.datasource = NpmDatasource.id; @@ -3209,6 +3237,7 @@ describe('workers/repository/process/lookup/index', () => { deprecated: true, repository: { url: null, directory: 'test' }, }; + returnJson.versions['1.4.1'].deprecated = 'true'; httpMock .scope('https://registry.npmjs.org') @@ -3221,6 +3250,14 @@ describe('workers/repository/process/lookup/index', () => { expect(res).toEqual({ currentVersion: '1.3.0', + currentVersionTimestamp: '2015-04-26T16:42:11.311Z', + deprecationMessage: codeBlock` + On registry \`https://registry.npmjs.org\`, the "latest" version of dependency \`q3\` has the following deprecation notice: + + \`true\` + + Marking the latest version of an npm package as deprecated results in the entire package being considered deprecated, so contact the package author you think this is a mistake. + `, fixedVersion: '1.3.0', isSingleVersion: true, registryUrl: 'https://registry.npmjs.org', @@ -3822,6 +3859,33 @@ describe('workers/repository/process/lookup/index', () => { }); }); + it('handles digest update for custom datasource', async () => { + config.currentValue = '1.0.0'; + config.packageName = 'my-package'; + config.datasource = CustomDatasource.id; + config.currentDigest = 'zzzzzzzzzzzzzzz'; + getCustomDatasourceReleases.mockResolvedValueOnce({ + releases: [ + { + version: '1.0.0', + newDigest: '0123456789abcdef', + }, + ], + }); + + const { updates } = await Result.wrap( + lookup.lookupUpdates(config), + ).unwrapOrThrow(); + + expect(updates).toEqual([ + { + newDigest: '0123456789abcdef', + newValue: '1.0.0', + updateType: 'digest', + }, + ]); + }); + it('handles digest update for non-version', async () => { config.currentValue = 'alpine'; config.packageName = 'node'; @@ -3979,14 +4043,15 @@ describe('workers/repository/process/lookup/index', () => { allowedVersions: '< 19.0.0', }, ]; + const releaseTimestamp = new Date( + Date.now() - 25 * 60 * 60 * 1000, + ).toISOString(); getDockerReleases.mockResolvedValueOnce({ releases: [ { version: '17.0.0', // a day old release - releaseTimestamp: new Date( - Date.now() - 25 * 60 * 60 * 1000, - ).toISOString(), + releaseTimestamp, }, { version: '18.0.0', @@ -4003,6 +4068,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res).toEqual({ currentVersion: '17.0.0', + currentVersionTimestamp: releaseTimestamp, fixedVersion: '17.0.0', isSingleVersion: true, registryUrl: 'https://index.docker.io', @@ -4018,7 +4084,6 @@ describe('workers/repository/process/lookup/index', () => { ], versioning: 'docker', warnings: [], - currentVersionTimestamp: undefined, }); }); @@ -4188,6 +4253,95 @@ describe('workers/repository/process/lookup/index', () => { ]); }); + it('handles replacements - Digest configured and validating getDigest funtion call', async () => { + config.packageName = 'openjdk'; + config.currentDigest = 'sha256:fedcba0987654321'; + config.currentValue = '17.0.0'; + //config.pinDigests = true; + config.datasource = DockerDatasource.id; + config.versioning = dockerVersioningId; + // This config is normally set when packageRules are applied + config.replacementName = 'eclipse-temurin'; + config.replacementVersion = '19.0.0'; + getDockerReleases.mockResolvedValueOnce({ + releases: [ + { + version: '17.0.0', + }, + { + version: '17.0.1', + }, + ], + lookupName: 'openjdk', + }); + getDockerDigest.mockResolvedValueOnce('sha256:abcdef1234567890'); + getDockerDigest.mockResolvedValueOnce('sha256:fedcba0987654321'); + getDockerDigest.mockResolvedValueOnce('sha256:pin0987654321'); + + const { updates } = await Result.wrap( + lookup.lookupUpdates(config), + ).unwrapOrThrow(); + + expect(updates).toEqual([ + { + bucket: 'non-major', + newDigest: 'sha256:abcdef1234567890', + newMajor: 17, + newMinor: 0, + newValue: '17.0.1', + newVersion: '17.0.1', + updateType: 'patch', + }, + { + newDigest: 'sha256:fedcba0987654321', + newName: 'eclipse-temurin', + newValue: '19.0.0', + newVersion: undefined, + updateType: 'replacement', + }, + { + newDigest: 'sha256:pin0987654321', + newValue: '17.0.0', + newVersion: undefined, + updateType: 'digest', + }, + ]); + + expect(getDockerDigest).toHaveBeenNthCalledWith( + 1, + { + currentDigest: 'sha256:fedcba0987654321', + currentValue: '17.0.0', + lookupName: 'openjdk', + packageName: 'openjdk', + registryUrl: 'https://index.docker.io', + }, + '17.0.1', + ); + expect(getDockerDigest).toHaveBeenNthCalledWith( + 2, + { + currentDigest: undefined, + currentValue: '17.0.0', + lookupName: undefined, + packageName: 'eclipse-temurin', + registryUrl: 'https://index.docker.io', + }, + '19.0.0', + ); + expect(getDockerDigest).toHaveBeenNthCalledWith( + 3, + { + currentDigest: 'sha256:fedcba0987654321', + currentValue: '17.0.0', + lookupName: 'openjdk', + packageName: 'openjdk', + registryUrl: 'https://index.docker.io', + }, + '17.0.0', + ); + }); + it('handles replacements - skips if package and replacement names match', async () => { config.packageName = 'openjdk'; config.currentValue = undefined; @@ -4496,5 +4650,42 @@ describe('workers/repository/process/lookup/index', () => { expect(updates).toBeEmptyArray(); }); }); + + it('detects gomod updates and uses updateType=digest when appropriate', async () => { + config.manager = 'gomod'; + config.datasource = GoDatasource.id; + config.currentValue = 'v0.0.0-20240506185236-b8a5c65736ae'; + config.currentDigest = 'b8a5c65736ae'; + config.packageName = 'google.golang.org/genproto/googleapis/rpc'; + config.digestOneAndOnly = true; + + httpMock + .scope( + 'https://proxy.golang.org/google.golang.org/genproto/googleapis/rpc', + ) + .get('/@v/list') + .reply(200, '') + .get('/v2/@v/list') + .reply(404) + .get('/@latest') + .reply(200, { Version: 'v0.0.0-20240509183442-62759503f434' }); + + const { updates } = await Result.wrap( + lookup.lookupUpdates(config), + ).unwrapOrThrow(); + + expect(updates).toEqual([ + { + bucket: 'non-major', + newDigest: '62759503f434', + newMajor: 0, + newMinor: 0, + newValue: 'v0.0.0-20240509183442-62759503f434', + newVersion: 'v0.0.0-20240509183442-62759503f434', + releaseTimestamp: '2024-05-09T18:34:42.000Z', + updateType: 'digest', + }, + ]); + }); }); }); diff --git a/lib/workers/repository/process/lookup/index.ts b/lib/workers/repository/process/lookup/index.ts index b564a6eb0984ab..953e21df73146e 100644 --- a/lib/workers/repository/process/lookup/index.ts +++ b/lib/workers/repository/process/lookup/index.ts @@ -4,6 +4,7 @@ import type { ValidationMessage } from '../../../../config/types'; import { CONFIG_VALIDATION } from '../../../../constants/error-messages'; import { logger } from '../../../../logger'; import { + GetDigestInputConfig, Release, ReleaseResult, applyDatasourceFilters, @@ -35,6 +36,17 @@ import { isReplacementRulesConfigured, } from './utils'; +function getTimestamp( + versions: Release[], + version: string, + versioning: allVersioning.VersioningApi, +): string | null | undefined { + return versions.find( + (v) => + versioning.isValid(v.version) && versioning.equals(v.version, version), + )?.releaseTimestamp; +} + export async function lookupUpdates( inconfig: LookupUpdateConfig, ): Promise> { @@ -61,6 +73,12 @@ export async function lookupUpdates( 'lookupUpdates', ); if (config.currentValue && !is.string(config.currentValue)) { + // If currentValue is not a string, then it's invalid + if (config.currentValue) { + logger.debug( + `Invalid currentValue for ${config.packageName}: ${JSON.stringify(config.currentValue)} (${typeof config.currentValue})`, + ); + } res.skipReason = 'invalid-value'; return Result.ok(res); } @@ -157,6 +175,8 @@ export async function lookupUpdates( 'homepage', 'changelogUrl', 'dependencyUrl', + 'lookupName', + 'packageScope', ]); const latestVersion = dependency.tags?.latest; @@ -269,27 +289,31 @@ export async function lookupUpdates( if (!currentVersion) { if (!config.lockedVersion) { + logger.debug( + `No currentVersion or lockedVersion found for ${config.packageName}`, + ); res.skipReason = 'invalid-value'; } return Result.ok(res); } res.currentVersion = currentVersion!; - const currentVersionTimestamp = allVersions.find( - (v) => - versioning.isValid(v.version) && - versioning.equals(v.version, currentVersion), - )?.releaseTimestamp; + const currentVersionTimestamp = getTimestamp( + allVersions, + currentVersion, + versioning, + ); - if ( - is.nonEmptyString(currentVersionTimestamp) && - config.packageRules?.some((rules) => - is.nonEmptyString(rules.matchCurrentAge), - ) - ) { + if (is.nonEmptyString(currentVersionTimestamp)) { res.currentVersionTimestamp = currentVersionTimestamp; - // Reapply package rules to check matches for matchCurrentAge - config = applyPackageRules({ ...config, currentVersionTimestamp }); + if ( + config.packageRules?.some((rules) => + is.nonEmptyString(rules.matchCurrentAge), + ) + ) { + // Reapply package rules to check matches for matchCurrentAge + config = applyPackageRules({ ...config, currentVersionTimestamp }); + } } if ( @@ -385,6 +409,17 @@ export async function lookupUpdates( bucket, release, ); + + // #29034 + if ( + config.manager === 'gomod' && + compareValue?.startsWith('v0.0.0-') && + update.newValue?.startsWith('v0.0.0-') && + config.currentDigest !== update.newDigest + ) { + update.updateType = 'digest'; + } + if (pendingChecks) { update.pendingChecks = pendingChecks; } @@ -423,6 +458,9 @@ export async function lookupUpdates( ); if (!config.pinDigests && !config.currentDigest) { + logger.debug( + `Skipping ${config.packageName} because no currentDigest or pinDigests`, + ); res.skipReason = 'invalid-value'; } else { delete res.skipReason; @@ -496,10 +534,31 @@ export async function lookupUpdates( // update digest for all for (const update of res.updates) { if (config.pinDigests === true || config.currentDigest) { + const getDigestConfig: GetDigestInputConfig = { + ...config, + registryUrl: update.registryUrl ?? res.registryUrl, + lookupName: res.lookupName, + }; + + // #20304 only pass it for replacement updates, otherwise we get wrong or invalid digest + if (update.updateType !== 'replacement') { + delete getDigestConfig.replacementName; + } + + // #20304 don't use lookupName and currentDigest when we replace image name + if ( + update.updateType === 'replacement' && + update.newName !== config.packageName + ) { + delete getDigestConfig.lookupName; + delete getDigestConfig.currentDigest; + } + // TODO #22198 update.newDigest ??= dependency?.releases.find((r) => r.version === update.newValue) - ?.newDigest ?? (await getDigest(config, update.newValue))!; + ?.newDigest ?? + (await getDigest(getDigestConfig, update.newValue))!; // If the digest could not be determined, report this as otherwise the // update will be omitted later on without notice. diff --git a/lib/workers/repository/process/lookup/types.ts b/lib/workers/repository/process/lookup/types.ts index 7d88590ca01292..ed944ea253d5e9 100644 --- a/lib/workers/repository/process/lookup/types.ts +++ b/lib/workers/repository/process/lookup/types.ts @@ -41,6 +41,7 @@ export interface LookupUpdateConfig isVulnerabilityAlert?: boolean; separateMajorMinor?: boolean; separateMultipleMajor?: boolean; + separateMultipleMinor?: boolean; datasource: string; packageName: string; minimumConfidence?: MergeConfidence | undefined; @@ -59,6 +60,7 @@ export interface UpdateResult { sourceUrl?: string | null; currentVersion?: string; isSingleVersion?: boolean; + lookupName?: string; skipReason?: SkipReason; registryUrl?: string; fixedVersion?: string; diff --git a/lib/workers/repository/process/lookup/update-type.ts b/lib/workers/repository/process/lookup/update-type.ts index a47375b4c64e77..9db72f6a56746e 100644 --- a/lib/workers/repository/process/lookup/update-type.ts +++ b/lib/workers/repository/process/lookup/update-type.ts @@ -4,6 +4,7 @@ import type * as allVersioning from '../../../../modules/versioning'; export interface UpdateTypeConfig { separateMajorMinor?: boolean; separateMultipleMajor?: boolean; + separateMultipleMinor?: boolean; separateMinorPatch?: boolean; } diff --git a/lib/workers/repository/reconfigure/index.ts b/lib/workers/repository/reconfigure/index.ts index 22ee0135f9356f..b27e7b5e50613a 100644 --- a/lib/workers/repository/reconfigure/index.ts +++ b/lib/workers/repository/reconfigure/index.ts @@ -151,7 +151,7 @@ export async function validateReconfigureBranch( } // perform validation and provide a passing or failing check run based on result - const validationResult = await validateConfig(false, configFileParsed); + const validationResult = await validateConfig('repo', configFileParsed); // failing check if (validationResult.errors.length > 0) { diff --git a/lib/workers/repository/result.ts b/lib/workers/repository/result.ts index 83829671885d8c..70ba5f0c57d9d4 100644 --- a/lib/workers/repository/result.ts +++ b/lib/workers/repository/result.ts @@ -12,6 +12,7 @@ import { REPOSITORY_DISABLED_BY_CONFIG, REPOSITORY_EMPTY, REPOSITORY_FORKED, + REPOSITORY_FORK_MODE_FORKED, REPOSITORY_MIRRORED, REPOSITORY_NOT_FOUND, REPOSITORY_NO_CONFIG, @@ -47,6 +48,7 @@ export function processResult( REPOSITORY_DISABLED, REPOSITORY_DISABLED_BY_CONFIG, REPOSITORY_EMPTY, + REPOSITORY_FORK_MODE_FORKED, REPOSITORY_FORKED, REPOSITORY_MIRRORED, REPOSITORY_NOT_FOUND, diff --git a/lib/workers/repository/stats.spec.ts b/lib/workers/repository/stats.spec.ts deleted file mode 100644 index aa584549f23dc7..00000000000000 --- a/lib/workers/repository/stats.spec.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { logger } from '../../../test/util'; -import type { Logger } from '../../logger/types'; -import * as memCache from '../../util/cache/memory'; -import type { LookupStats } from '../../util/cache/memory/types'; -import { printLookupStats, printRequestStats } from './stats'; - -const log = logger.logger as jest.Mocked; - -describe('workers/repository/stats', () => { - beforeEach(() => { - memCache.init(); - }); - - describe('printLookupStats()', () => { - it('runs', () => { - const stats: LookupStats[] = [ - { - datasource: 'npm', - duration: 100, - }, - { - datasource: 'npm', - duration: 200, - }, - { - datasource: 'docker', - duration: 1000, - }, - ]; - memCache.set('lookup-stats', stats); - expect(printLookupStats()).toBeUndefined(); - expect(log.debug).toHaveBeenCalledTimes(1); - expect(log.debug.mock.calls[0][0]).toMatchInlineSnapshot(` - { - "docker": { - "averageMs": 1000, - "count": 1, - "maximumMs": 1000, - "totalMs": 1000, - }, - "npm": { - "averageMs": 150, - "count": 2, - "maximumMs": 200, - "totalMs": 300, - }, - } - `); - }); - }); - - describe('printRequestStats()', () => { - it('runs', () => { - memCache.set('package-cache-gets', [30, 100, 10, 20]); - memCache.set('package-cache-sets', [110, 80, 20]); - memCache.set('http-requests', [ - { - method: 'get', - url: 'https://api.github.com/api/v3/user', - duration: 100, - queueDuration: 0, - statusCode: 200, - }, - { - method: 'post', - url: 'https://api.github.com/graphql', - duration: 130, - queueDuration: 0, - statusCode: 401, - }, - { - method: 'post', - url: 'https://api.github.com/graphql', - duration: 150, - queueDuration: 0, - statusCode: 200, - }, - { - method: 'post', - url: 'https://api.github.com/graphql', - duration: 20, - queueDuration: 10, - statusCode: 200, - }, - { - method: 'get', - url: 'https://api.github.com/api/v3/repositories', - duration: 500, - queueDuration: 0, - statusCode: 500, - }, - { - method: 'get', - url: 'https://auth.docker.io', - duration: 200, - queueDuration: 0, - statusCode: 401, - }, - ]); - expect(printRequestStats()).toBeUndefined(); - expect(log.trace).toHaveBeenCalledOnce(); - expect(log.debug).toHaveBeenCalledTimes(2); - expect(log.trace.mock.calls[0][0]).toMatchInlineSnapshot(` - { - "allRequests": [ - "GET https://api.github.com/api/v3/repositories 500 500 0", - "GET https://api.github.com/api/v3/user 200 100 0", - "POST https://api.github.com/graphql 401 130 0", - "POST https://api.github.com/graphql 200 150 0", - "POST https://api.github.com/graphql 200 20 10", - "GET https://auth.docker.io 401 200 0", - ], - "requestHosts": { - "api.github.com": [ - { - "duration": 500, - "method": "get", - "queueDuration": 0, - "statusCode": 500, - "url": "https://api.github.com/api/v3/repositories", - }, - { - "duration": 100, - "method": "get", - "queueDuration": 0, - "statusCode": 200, - "url": "https://api.github.com/api/v3/user", - }, - { - "duration": 130, - "method": "post", - "queueDuration": 0, - "statusCode": 401, - "url": "https://api.github.com/graphql", - }, - { - "duration": 150, - "method": "post", - "queueDuration": 0, - "statusCode": 200, - "url": "https://api.github.com/graphql", - }, - { - "duration": 20, - "method": "post", - "queueDuration": 10, - "statusCode": 200, - "url": "https://api.github.com/graphql", - }, - ], - "auth.docker.io": [ - { - "duration": 200, - "method": "get", - "queueDuration": 0, - "statusCode": 401, - "url": "https://auth.docker.io", - }, - ], - }, - } - `); - expect(log.debug.mock.calls[0][0]).toMatchInlineSnapshot(` - { - "get": { - "avgMs": 40, - "count": 4, - "maxMs": 100, - "medianMs": 20, - "totalMs": 160, - }, - "set": { - "avgMs": 70, - "count": 3, - "maxMs": 110, - "medianMs": 80, - "totalMs": 210, - }, - } - `); - expect(log.debug.mock.calls[1][0]).toMatchInlineSnapshot(` - { - "hostStats": { - "api.github.com": { - "queueAvgMs": 2, - "requestAvgMs": 180, - "requestCount": 5, - }, - "auth.docker.io": { - "queueAvgMs": 0, - "requestAvgMs": 200, - "requestCount": 1, - }, - }, - "totalRequests": 6, - "urls": { - "https://api.github.com/api/v3/repositories (GET,500)": 1, - "https://api.github.com/api/v3/user (GET,200)": 1, - "https://api.github.com/graphql (POST,200)": 2, - "https://api.github.com/graphql (POST,401)": 1, - "https://auth.docker.io (GET,401)": 1, - }, - } - `); - }); - }); -}); diff --git a/lib/workers/repository/stats.ts b/lib/workers/repository/stats.ts deleted file mode 100644 index 157c4b3491649d..00000000000000 --- a/lib/workers/repository/stats.ts +++ /dev/null @@ -1,145 +0,0 @@ -import URL from 'node:url'; -import { logger } from '../../logger'; -import { sortNumeric } from '../../util/array'; -import * as memCache from '../../util/cache/memory'; -import type { LookupStats } from '../../util/cache/memory/types'; -import type { RequestStats } from '../../util/http/types'; - -interface CacheStats { - count: number; - avgMs?: number; - medianMs?: number; - maxMs?: number; - totalMs?: number; -} - -export function printLookupStats(): void { - const lookups = memCache.get('lookup-stats') ?? []; - const datasourceDurations: Record = {}; - for (const lookup of lookups) { - datasourceDurations[lookup.datasource] ??= []; - datasourceDurations[lookup.datasource].push(lookup.duration); - } - const data: Record = {}; - for (const [datasource, durations] of Object.entries(datasourceDurations)) { - const count = durations.length; - const totalMs = durations.reduce((a, c) => a + c, 0); - const averageMs = Math.round(totalMs / count); - const maximumMs = Math.max(...durations); - data[datasource] = { count, averageMs, totalMs, maximumMs }; - } - logger.debug(data, 'Package lookup durations'); -} - -export function printRequestStats(): void { - const packageCacheGets = ( - memCache.get('package-cache-gets') ?? [] - ).sort(sortNumeric); - const packageCacheSets = ( - memCache.get('package-cache-sets') ?? [] - ).sort(sortNumeric); - const packageCacheStats: Record = { - get: { - count: packageCacheGets.length, - }, - set: { - count: packageCacheSets.length, - }, - }; - if (packageCacheGets.length) { - packageCacheStats.get.totalMs = Math.round( - packageCacheGets.reduce((a, b) => a + b, 0), - ); - packageCacheStats.get.avgMs = Math.round( - packageCacheStats.get.totalMs / packageCacheGets.length, - ); - if (packageCacheGets.length > 1) { - packageCacheStats.get.medianMs = - packageCacheGets[Math.round(packageCacheGets.length / 2) - 1]; - packageCacheStats.get.maxMs = - packageCacheGets[packageCacheGets.length - 1]; - } - } - if (packageCacheSets.length) { - packageCacheStats.set.totalMs = Math.round( - packageCacheSets.reduce((a, b) => a + b, 0), - ); - packageCacheStats.set.avgMs = Math.round( - packageCacheStats.set.totalMs / packageCacheSets.length, - ); - if (packageCacheSets.length > 1) { - packageCacheStats.set.medianMs = - packageCacheSets[Math.round(packageCacheSets.length / 2) - 1]; - packageCacheStats.set.maxMs = - packageCacheSets[packageCacheSets.length - 1]; - } - } - logger.debug(packageCacheStats, 'Package cache statistics'); - const httpRequests = memCache.get('http-requests'); - // istanbul ignore next - if (!httpRequests) { - return; - } - httpRequests.sort((a, b) => { - if (a.url === b.url) { - return 0; - } - if (a.url < b.url) { - return -1; - } - return 1; - }); - const allRequests: string[] = []; - const requestHosts: Record = {}; - const rawUrls: Record = {}; - for (const httpRequest of httpRequests) { - const { method, url, duration, queueDuration, statusCode } = httpRequest; - const [baseUrl] = url.split('?'); - // put method last for better sorting - const urlKey = `${baseUrl} (${method.toUpperCase()},${statusCode})`; - if (rawUrls[urlKey]) { - rawUrls[urlKey] += 1; - } else { - rawUrls[urlKey] = 1; - } - allRequests.push( - `${method.toUpperCase()} ${url} ${statusCode} ${duration} ${queueDuration}`, - ); - const { hostname } = URL.parse(url); - - // istanbul ignore if: TODO: fix types (#9610) - if (!hostname) { - return; - } - requestHosts[hostname] = requestHosts[hostname] || []; - requestHosts[hostname].push(httpRequest); - } - const urls: Record = {}; - // Sort urls for easier reading - for (const url of Object.keys(rawUrls).sort()) { - urls[url] = rawUrls[url]; - } - logger.trace({ allRequests, requestHosts }, 'full stats'); - type HostStats = { - requestCount: number; - requestAvgMs: number; - queueAvgMs: number; - }; - const hostStats: Record = {}; - let totalRequests = 0; - for (const [hostname, requests] of Object.entries(requestHosts)) { - const requestCount = requests.length; - totalRequests += requestCount; - const requestSum = requests - .map(({ duration }) => duration) - .reduce((a, b) => a + b, 0); - const requestAvgMs = Math.round(requestSum / requestCount); - - const queueSum = requests - .map(({ queueDuration }) => queueDuration) - .reduce((a, b) => a + b, 0); - const queueAvgMs = Math.round(queueSum / requestCount); - hostStats[hostname] = { requestCount, requestAvgMs, queueAvgMs }; - } - logger.debug({ urls, hostStats, totalRequests }, 'http statistics'); -} diff --git a/lib/workers/repository/update/branch/__snapshots__/get-updated.spec.ts.snap b/lib/workers/repository/update/branch/__snapshots__/get-updated.spec.ts.snap index 55675aa4f8d44a..96c1cbea0768ab 100644 --- a/lib/workers/repository/update/branch/__snapshots__/get-updated.spec.ts.snap +++ b/lib/workers/repository/update/branch/__snapshots__/get-updated.spec.ts.snap @@ -3,6 +3,7 @@ exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() bumps versions in autoReplace managers 1`] = ` { "artifactErrors": [], + "artifactNotices": [], "reuseExistingBranch": undefined, "updatedArtifacts": [], "updatedPackageFiles": [ @@ -18,6 +19,7 @@ exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() b exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() bumps versions in updateDependency managers 1`] = ` { "artifactErrors": [], + "artifactNotices": [], "reuseExistingBranch": undefined, "updatedArtifacts": [], "updatedPackageFiles": [ @@ -33,6 +35,7 @@ exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() b exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() handles autoreplace base updated 1`] = ` { "artifactErrors": [], + "artifactNotices": [], "reuseExistingBranch": undefined, "updatedArtifacts": [], "updatedPackageFiles": [ @@ -48,6 +51,7 @@ exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() h exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() handles autoreplace branch needs update 1`] = ` { "artifactErrors": [], + "artifactNotices": [], "reuseExistingBranch": false, "updatedArtifacts": [], "updatedPackageFiles": [ @@ -63,6 +67,7 @@ exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() h exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() handles content change 1`] = ` { "artifactErrors": [], + "artifactNotices": [], "reuseExistingBranch": false, "updatedArtifacts": [], "updatedPackageFiles": [ @@ -78,6 +83,7 @@ exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() h exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() handles git submodules 1`] = ` { "artifactErrors": [], + "artifactNotices": [], "reuseExistingBranch": undefined, "updatedArtifacts": [], "updatedPackageFiles": [ @@ -93,6 +99,7 @@ exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() h exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() handles isRemediation rebase 1`] = ` { "artifactErrors": [], + "artifactNotices": [], "reuseExistingBranch": false, "updatedArtifacts": [], "updatedPackageFiles": [ @@ -108,6 +115,7 @@ exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() h exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() handles isRemediation success 1`] = ` { "artifactErrors": [], + "artifactNotices": [], "reuseExistingBranch": undefined, "updatedArtifacts": [], "updatedPackageFiles": [ @@ -128,6 +136,7 @@ exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() h "stderr": "some error", }, ], + "artifactNotices": [], "reuseExistingBranch": true, "updatedArtifacts": [], "updatedPackageFiles": [ @@ -143,6 +152,7 @@ exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() h exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() handles lock files 1`] = ` { "artifactErrors": [], + "artifactNotices": [], "reuseExistingBranch": true, "updatedArtifacts": [ { @@ -164,6 +174,7 @@ exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() h exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() handles lockFileMaintenance 1`] = ` { "artifactErrors": [], + "artifactNotices": [], "reuseExistingBranch": undefined, "updatedArtifacts": [ { @@ -184,6 +195,7 @@ exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() h "stderr": "some error", }, ], + "artifactNotices": [], "reuseExistingBranch": undefined, "updatedArtifacts": [], "updatedPackageFiles": [], @@ -193,6 +205,7 @@ exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() h exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() update artifacts on update-lockfile strategy 1`] = ` { "artifactErrors": [], + "artifactNotices": [], "reuseExistingBranch": undefined, "updatedArtifacts": [ { @@ -214,6 +227,7 @@ exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() u exports[`workers/repository/update/branch/get-updated getUpdatedPackageFiles() update artifacts on update-lockfile strategy with no updateLockedDependency 1`] = ` { "artifactErrors": [], + "artifactNotices": [], "reuseExistingBranch": undefined, "updatedArtifacts": [ { diff --git a/lib/workers/repository/update/branch/get-updated.spec.ts b/lib/workers/repository/update/branch/get-updated.spec.ts index 123485920d9c87..64a614e517d086 100644 --- a/lib/workers/repository/update/branch/get-updated.spec.ts +++ b/lib/workers/repository/update/branch/get-updated.spec.ts @@ -1,9 +1,11 @@ +import { mockDeep } from 'jest-mock-extended'; import { git, mocked } from '../../../../../test/util'; import { GitRefsDatasource } from '../../../../modules/datasource/git-refs'; import * as _batectWrapper from '../../../../modules/manager/batect-wrapper'; import * as _bundler from '../../../../modules/manager/bundler'; import * as _composer from '../../../../modules/manager/composer'; import * as _gitSubmodules from '../../../../modules/manager/git-submodules'; +import * as _gomod from '../../../../modules/manager/gomod'; import * as _helmv3 from '../../../../modules/manager/helmv3'; import * as _npm from '../../../../modules/manager/npm'; import * as _pep621 from '../../../../modules/manager/pep621'; @@ -16,6 +18,7 @@ import { getUpdatedPackageFiles } from './get-updated'; const bundler = mocked(_bundler); const composer = mocked(_composer); const gitSubmodules = mocked(_gitSubmodules); +const gomod = mocked(_gomod); const helmv3 = mocked(_helmv3); const npm = mocked(_npm); const batectWrapper = mocked(_batectWrapper); @@ -28,6 +31,7 @@ jest.mock('../../../../modules/manager/composer'); jest.mock('../../../../modules/manager/helmv3'); jest.mock('../../../../modules/manager/npm'); jest.mock('../../../../modules/manager/git-submodules'); +jest.mock('../../../../modules/manager/gomod', () => mockDeep()); jest.mock('../../../../modules/manager/batect-wrapper'); jest.mock('../../../../modules/manager/pep621'); jest.mock('../../../../modules/manager/poetry'); @@ -77,6 +81,7 @@ describe('workers/repository/update/branch/get-updated', () => { reuseExistingBranch: undefined, updatedArtifacts: [], updatedPackageFiles: [], + artifactNotices: [], }); }); @@ -110,6 +115,7 @@ describe('workers/repository/update/branch/get-updated', () => { reuseExistingBranch: undefined, updatedArtifacts: [], updatedPackageFiles: [], + artifactNotices: [], }); }); @@ -178,6 +184,54 @@ describe('workers/repository/update/branch/get-updated', () => { }); }); + it('handles artifact notices', async () => { + config.reuseExistingBranch = true; + config.upgrades.push({ + packageFile: 'go.mod', + manager: 'gomod', + branchName: 'foo/bar', + }); + gomod.updateDependency.mockReturnValue('some new content'); + gomod.updateArtifacts.mockResolvedValueOnce([ + { + file: { + type: 'addition', + path: 'go.mod', + contents: 'some content', + }, + notice: { + file: 'go.mod', + message: 'some notice', + }, + }, + ]); + const res = await getUpdatedPackageFiles(config); + expect(res).toEqual({ + artifactErrors: [], + artifactNotices: [ + { + file: 'go.mod', + message: 'some notice', + }, + ], + reuseExistingBranch: false, + updatedArtifacts: [ + { + contents: 'some content', + path: 'go.mod', + type: 'addition', + }, + ], + updatedPackageFiles: [ + { + contents: 'some new content', + path: 'go.mod', + type: 'addition', + }, + ], + }); + }); + it('handles lockFileMaintenance', async () => { config.upgrades.push({ manager: 'composer', @@ -205,6 +259,81 @@ describe('workers/repository/update/branch/get-updated', () => { }); }); + it('for updatedArtifacts passes proper lockFiles', async () => { + config.upgrades.push({ + packageFile: 'composer.json', + manager: 'composer', + branchName: '', + }); + config.lockFiles = ['different.lock']; + config.packageFiles = { + composer: [ + { + packageFile: 'composer.json', + lockFiles: ['composer.lock'], + deps: [], + }, + ] satisfies PackageFile[], + }; + autoReplace.doAutoReplace.mockResolvedValueOnce('some new content'); + composer.updateArtifacts.mockResolvedValueOnce([ + { + file: { + type: 'addition', + path: 'composer.lock', + contents: 'some contents', + }, + }, + ]); + await getUpdatedPackageFiles(config); + expect(composer.updateArtifacts).toHaveBeenCalledWith( + expect.objectContaining({ + config: expect.objectContaining({ + lockFiles: ['composer.lock'], + }), + }), + ); + }); + + it('for nonUpdatedArtifacts passes proper lockFiles', async () => { + config.upgrades.push({ + packageFile: 'composer.json', + manager: 'composer', + branchName: '', + isLockfileUpdate: true, + }); + composer.updateLockedDependency.mockReturnValueOnce({ + status: 'unsupported', + }); + config.lockFiles = ['different.lock']; + config.packageFiles = { + composer: [ + { + packageFile: 'composer.json', + lockFiles: ['composer.lock'], + deps: [], + }, + ] satisfies PackageFile[], + }; + composer.updateArtifacts.mockResolvedValueOnce([ + { + file: { + type: 'addition', + path: 'composer.lock', + contents: 'some contents', + }, + }, + ]); + await getUpdatedPackageFiles(config); + expect(composer.updateArtifacts).toHaveBeenCalledWith( + expect.objectContaining({ + config: expect.objectContaining({ + lockFiles: ['composer.lock'], + }), + }), + ); + }); + it('for lockFileMaintenance passes proper lockFiles', async () => { config.upgrades.push({ manager: 'composer', @@ -278,6 +407,7 @@ describe('workers/repository/update/branch/get-updated', () => { expect(res).toMatchInlineSnapshot(` { "artifactErrors": [], + "artifactNotices": [], "reuseExistingBranch": undefined, "updatedArtifacts": [], "updatedPackageFiles": [], @@ -458,6 +588,7 @@ describe('workers/repository/update/branch/get-updated', () => { expect(res).toMatchInlineSnapshot(` { "artifactErrors": [], + "artifactNotices": [], "reuseExistingBranch": undefined, "updatedArtifacts": [], "updatedPackageFiles": [], @@ -481,6 +612,7 @@ describe('workers/repository/update/branch/get-updated', () => { expect(res).toMatchInlineSnapshot(` { "artifactErrors": [], + "artifactNotices": [], "reuseExistingBranch": false, "updatedArtifacts": [], "updatedPackageFiles": [], @@ -506,6 +638,7 @@ describe('workers/repository/update/branch/get-updated', () => { expect(res).toMatchInlineSnapshot(` { "artifactErrors": [], + "artifactNotices": [], "reuseExistingBranch": false, "updatedArtifacts": [], "updatedPackageFiles": [], diff --git a/lib/workers/repository/update/branch/get-updated.ts b/lib/workers/repository/update/branch/get-updated.ts index e3dbf85f776701..d0f6a57f3e5717 100644 --- a/lib/workers/repository/update/branch/get-updated.ts +++ b/lib/workers/repository/update/branch/get-updated.ts @@ -5,6 +5,7 @@ import { logger } from '../../../../logger'; import { get } from '../../../../modules/manager'; import type { ArtifactError, + ArtifactNotice, PackageDependency, PackageFile, UpdateArtifact, @@ -21,6 +22,7 @@ export interface PackageFilesResult { reuseExistingBranch?: boolean; updatedPackageFiles: FileChange[]; updatedArtifacts: FileChange[]; + artifactNotices: ArtifactNotice[]; } async function getFileContent( @@ -288,6 +290,11 @@ export async function getUpdatedPackageFiles( })); const updatedArtifacts: FileChange[] = []; const artifactErrors: ArtifactError[] = []; + const artifactNotices: ArtifactNotice[] = []; + // istanbul ignore if + if (is.nonEmptyArray(updatedPackageFiles)) { + logger.debug('updateArtifacts for updatedPackageFiles'); + } for (const packageFile of updatedPackageFiles) { const updatedDeps = packageFileUpdatedDeps[packageFile.path]; const managers = packageFileManagers[packageFile.path]; @@ -298,9 +305,18 @@ export async function getUpdatedPackageFiles( updatedDeps, // TODO #22198 newPackageFileContent: packageFile.contents!.toString(), - config, + config: patchConfigForArtifactsUpdate( + config, + manager, + packageFile.path, + ), }); - processUpdateArtifactResults(results, updatedArtifacts, artifactErrors); + processUpdateArtifactResults( + results, + updatedArtifacts, + artifactErrors, + artifactNotices, + ); } } } @@ -311,6 +327,10 @@ export async function getUpdatedPackageFiles( path: name, contents: nonUpdatedFileContents[name], })); + // istanbul ignore if + if (is.nonEmptyArray(nonUpdatedPackageFiles)) { + logger.debug('updateArtifacts for nonUpdatedPackageFiles'); + } for (const packageFile of nonUpdatedPackageFiles) { const updatedDeps = packageFileUpdatedDeps[packageFile.path]; const managers = packageFileManagers[packageFile.path]; @@ -321,9 +341,18 @@ export async function getUpdatedPackageFiles( updatedDeps, // TODO #22198 newPackageFileContent: packageFile.contents!.toString(), - config, + config: patchConfigForArtifactsUpdate( + config, + manager, + packageFile.path, + ), }); - processUpdateArtifactResults(results, updatedArtifacts, artifactErrors); + processUpdateArtifactResults( + results, + updatedArtifacts, + artifactErrors, + artifactNotices, + ); if (is.nonEmptyArray(results)) { updatedPackageFiles.push(packageFile); } @@ -332,6 +361,10 @@ export async function getUpdatedPackageFiles( } if (!reuseExistingBranch) { // Only perform lock file maintenance if it's a fresh commit + // istanbul ignore if + if (is.nonEmptyArray(lockFileMaintenanceFiles)) { + logger.debug('updateArtifacts for lockFileMaintenanceFiles'); + } for (const packageFileName of lockFileMaintenanceFiles) { const managers = packageFileManagers[packageFileName]; if (is.nonEmptySet(managers)) { @@ -353,6 +386,7 @@ export async function getUpdatedPackageFiles( results, updatedArtifacts, artifactErrors, + artifactNotices, ); } } @@ -363,6 +397,7 @@ export async function getUpdatedPackageFiles( updatedPackageFiles, updatedArtifacts, artifactErrors, + artifactNotices, }; } @@ -405,16 +440,22 @@ function processUpdateArtifactResults( results: UpdateArtifactsResult[] | null, updatedArtifacts: FileChange[], artifactErrors: ArtifactError[], + artifactNotices: ArtifactNotice[], ): void { if (is.nonEmptyArray(results)) { for (const res of results) { - const { file, artifactError } = res; - // istanbul ignore else + const { file, notice, artifactError } = res; if (file) { updatedArtifacts.push(file); - } else if (artifactError) { + } + + if (artifactError) { artifactErrors.push(artifactError); } + + if (notice) { + artifactNotices.push(notice); + } } } } diff --git a/lib/workers/repository/update/branch/index.spec.ts b/lib/workers/repository/update/branch/index.spec.ts index 511063135527a5..99be0cfac0beb2 100644 --- a/lib/workers/repository/update/branch/index.spec.ts +++ b/lib/workers/repository/update/branch/index.spec.ts @@ -1,3 +1,4 @@ +import { codeBlock } from 'common-tags'; import { fs, git, @@ -103,11 +104,13 @@ describe('workers/repository/update/branch/index', () => { updatedPackageFiles: [], artifactErrors: [], updatedArtifacts: [], + artifactNotices: [], }; beforeEach(() => { scm.branchExists.mockResolvedValue(false); prWorker.ensurePr = jest.fn(); + prWorker.getPlatformPrOptions = jest.fn(); prAutomerge.checkAutoMerge = jest.fn(); // TODO: incompatible types (#22198) config = { @@ -133,6 +136,9 @@ describe('workers/repository/update/branch/index', () => { state: '', }), }); + prWorker.getPlatformPrOptions.mockReturnValue({ + usePlatformAutomerge: true, + }); GlobalConfig.set(adminConfig); // TODO: fix types, jest is using wrong overload (#22198) sanitize.sanitize.mockImplementation((input) => input!); @@ -539,6 +545,42 @@ describe('workers/repository/update/branch/index', () => { }); }); + it('returns if branch does not exist and in silent mode', async () => { + getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ + ...updatedPackageFiles, + }); + npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({ + artifactErrors: [], + updatedArtifacts: [], + }); + scm.branchExists.mockResolvedValue(false); + GlobalConfig.set({ ...adminConfig }); + config.mode = 'silent'; + expect(await branchWorker.processBranch(config)).toEqual({ + branchExists: false, + prNo: undefined, + result: 'needs-approval', + }); + }); + + it('returns if branch needs dependencyDashboardApproval', async () => { + getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ + ...updatedPackageFiles, + }); + npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({ + artifactErrors: [], + updatedArtifacts: [], + }); + scm.branchExists.mockResolvedValue(false); + GlobalConfig.set({ ...adminConfig }); + config.dependencyDashboardApproval = true; + expect(await branchWorker.processBranch(config)).toEqual({ + branchExists: false, + prNo: undefined, + result: 'needs-approval', + }); + }); + it('returns if pr creation limit exceeded and branch exists', async () => { getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ ...updatedPackageFiles, @@ -855,6 +897,40 @@ describe('workers/repository/update/branch/index', () => { expect(prWorker.ensurePr).toHaveBeenCalledTimes(0); }); + it('ensures PR and comments notice', async () => { + getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce( + partial({ + updatedPackageFiles: [partial()], + artifactNotices: [{ file: 'go.mod', message: 'some notice' }], + }), + ); + npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({ + artifactErrors: [], + updatedArtifacts: [partial()], + }); + scm.branchExists.mockResolvedValue(true); + automerge.tryBranchAutomerge.mockResolvedValueOnce('failed'); + prWorker.ensurePr.mockResolvedValueOnce({ + type: 'with-pr', + pr: partial({ number: 123 }), + }); + prAutomerge.checkAutoMerge.mockResolvedValueOnce({ automerged: true }); + commit.commitFilesToBranch.mockResolvedValueOnce(null); + await branchWorker.processBranch({ ...config, automerge: true }); + expect(prWorker.ensurePr).toHaveBeenCalledTimes(1); + expect(platform.ensureCommentRemoval).toHaveBeenCalledTimes(0); + expect(prAutomerge.checkAutoMerge).toHaveBeenCalledTimes(1); + expect(platform.ensureComment).toHaveBeenCalledWith({ + content: codeBlock` + ##### File name: go.mod + + some notice + `, + number: 123, + topic: 'ℹ Artifact update notice', + }); + }); + it('ensures PR and tries automerge', async () => { getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce( partial({ @@ -907,6 +983,35 @@ describe('workers/repository/update/branch/index', () => { expect(prAutomerge.checkAutoMerge).toHaveBeenCalledTimes(1); }); + it('ensures PR when impossible to automerge with mismatch keepUpdatedLabel', async () => { + getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce( + partial({ + updatedPackageFiles: [partial()], + }), + ); + npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({ + artifactErrors: [], + updatedArtifacts: [partial()], + }); + scm.branchExists.mockResolvedValue(true); + automerge.tryBranchAutomerge.mockResolvedValueOnce('stale'); + prWorker.ensurePr.mockResolvedValueOnce({ + type: 'with-pr', + pr: partial({ labels: ['keep-not-updated'] }), + }); + prAutomerge.checkAutoMerge.mockResolvedValueOnce({ automerged: false }); + commit.commitFilesToBranch.mockResolvedValueOnce(null); + await branchWorker.processBranch({ + ...config, + automerge: true, + rebaseWhen: 'conflicted', + keepUpdatedLabel: 'keep-updated', + }); + expect(prWorker.ensurePr).toHaveBeenCalledTimes(1); + expect(platform.ensureCommentRemoval).toHaveBeenCalledTimes(0); + expect(prAutomerge.checkAutoMerge).toHaveBeenCalledTimes(1); + }); + it('skips when automerge is off schedule', async () => { getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce( partial(), @@ -1205,6 +1310,7 @@ describe('workers/repository/update/branch/index', () => { updatedPackageFiles: [partial()], updatedArtifacts: [partial()], artifactErrors: [{}], + artifactNotices: [], }); npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({ artifactErrors: [], @@ -1477,6 +1583,7 @@ describe('workers/repository/update/branch/index', () => { updatedPackageFiles: [updatedPackageFile], artifactErrors: [], updatedArtifacts: [], + artifactNotices: [], }); npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({ artifactErrors: [], @@ -1574,6 +1681,7 @@ describe('workers/repository/update/branch/index', () => { updatedPackageFiles: [updatedPackageFile], artifactErrors: [], updatedArtifacts: [], + artifactNotices: [], } satisfies PackageFilesResult); npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({ artifactErrors: [], @@ -1658,6 +1766,7 @@ describe('workers/repository/update/branch/index', () => { updatedPackageFiles: [updatedPackageFile], artifactErrors: [], updatedArtifacts: [], + artifactNotices: [], }); npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({ artifactErrors: [], @@ -2037,6 +2146,60 @@ describe('workers/repository/update/branch/index', () => { expect(commit.commitFilesToBranch).not.toHaveBeenCalled(); }); + it('continues when rebaseWhen=never and keepUpdatedLabel', async () => { + getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ + ...updatedPackageFiles, + }); + npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({ + artifactErrors: [], + updatedArtifacts: [], + }); + scm.branchExists.mockResolvedValue(true); + platform.getBranchPr.mockResolvedValueOnce( + partial({ + state: 'open', + title: '', + labels: ['keep-updated'], + }), + ); + commit.commitFilesToBranch.mockResolvedValueOnce(null); + expect( + await branchWorker.processBranch({ + ...config, + rebaseWhen: 'never', + keepUpdatedLabel: 'keep-updated', + }), + ).toMatchObject({ result: 'done' }); + expect(commit.commitFilesToBranch).toHaveBeenCalled(); + }); + + it('returns when rebaseWhen=never and keepUpdatedLabel does not match', async () => { + getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ + ...updatedPackageFiles, + }); + npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({ + artifactErrors: [], + updatedArtifacts: [], + }); + scm.branchExists.mockResolvedValue(true); + platform.getBranchPr.mockResolvedValueOnce( + partial({ + state: 'open', + title: '', + labels: ['keep-updated'], + }), + ); + commit.commitFilesToBranch.mockResolvedValueOnce(null); + expect( + await branchWorker.processBranch({ + ...config, + rebaseWhen: 'never', + keepUpdatedLabel: 'keep-not-updated', + }), + ).toMatchObject({ result: 'no-work' }); + expect(commit.commitFilesToBranch).not.toHaveBeenCalled(); + }); + it('continues when rebaseWhen=never but checked', async () => { getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ ...updatedPackageFiles, diff --git a/lib/workers/repository/update/branch/index.ts b/lib/workers/repository/update/branch/index.ts index 050ce7403f71e6..c241925cdb8cc4 100644 --- a/lib/workers/repository/update/branch/index.ts +++ b/lib/workers/repository/update/branch/index.ts @@ -36,7 +36,7 @@ import * as template from '../../../../util/template'; import { isLimitReached } from '../../../global/limits'; import type { BranchConfig, BranchResult, PrBlockedBy } from '../../../types'; import { embedChangelogs } from '../../changelog'; -import { ensurePr } from '../pr'; +import { ensurePr, getPlatformPrOptions } from '../pr'; import { checkAutoMerge } from '../pr/automerge'; import { setArtifactErrorStatus } from './artifacts'; import { tryBranchAutomerge } from './automerge'; @@ -145,7 +145,11 @@ export async function processBranch( config.rebaseRequested = await rebaseCheck(config, branchPr); logger.debug(`PR rebase requested=${config.rebaseRequested}`); } + const keepUpdatedLabel = config.keepUpdatedLabel; const artifactErrorTopic = emojify(':warning: Artifact update problem'); + const artifactNoticeTopic = emojify( + ':information_source: Artifact update notice', + ); try { // Check if branch already existed const existingPr = @@ -182,12 +186,21 @@ export async function processBranch( result: 'pending', }; } - // istanbul ignore if - if (!branchExists && config.dependencyDashboardApproval) { - if (dependencyDashboardCheck) { - logger.debug(`Branch ${config.branchName} is approved for creation`); - } else { - logger.debug(`Branch ${config.branchName} needs approval`); + if (!branchExists) { + if (config.mode === 'silent' && !dependencyDashboardCheck) { + logger.debug( + `Branch ${config.branchName} creation is disabled because mode=silent`, + ); + return { + branchExists, + prNo: branchPr?.number, + result: 'needs-approval', + }; + } + if (config.dependencyDashboardApproval && !dependencyDashboardCheck) { + logger.debug( + `Branch ${config.branchName} creation is disabled because dependencyDashboardApproval=true`, + ); return { branchExists, prNo: branchPr?.number, @@ -429,6 +442,7 @@ export async function processBranch( } else if ( branchExists && config.rebaseWhen === 'never' && + !(keepUpdatedLabel && branchPr?.labels?.includes(keepUpdatedLabel)) && !dependencyDashboardCheck ) { logger.debug('rebaseWhen=never so skipping branch update check'); @@ -540,6 +554,14 @@ export async function processBranch( number: branchPr.number, topic: artifactErrorTopic, }); + + if (!config.artifactNotices?.length) { + await ensureCommentRemoval({ + type: 'by-topic', + number: branchPr.number, + topic: artifactNoticeTopic, + }); + } } } const forcedManually = userRebaseRequested || !branchExists; @@ -570,9 +592,22 @@ export async function processBranch( await scm.checkoutBranch(config.baseBranch); updatesVerified = true; } - // istanbul ignore if - if (branchPr && platform.refreshPr) { - await platform.refreshPr(branchPr.number); + + if (branchPr) { + const platformOptions = getPlatformPrOptions(config); + if ( + platformOptions.usePlatformAutomerge && + platform.reattemptPlatformAutomerge + ) { + await platform.reattemptPlatformAutomerge({ + number: branchPr.number, + platformOptions, + }); + } + // istanbul ignore if + if (platform.refreshPr) { + await platform.refreshPr(branchPr.number); + } } if (!commitSha && !branchExists) { return { @@ -636,7 +671,8 @@ export async function processBranch( } if ( mergeStatus === 'stale' && - ['conflicted', 'never'].includes(config.rebaseWhen!) + ['conflicted', 'never'].includes(config.rebaseWhen!) && + !(keepUpdatedLabel && branchPr?.labels?.includes(keepUpdatedLabel)) ) { logger.warn( 'Branch cannot automerge because it is behind base branch and rebaseWhen setting disallows rebasing - raising a PR instead', @@ -849,22 +885,38 @@ export async function processBranch( }); } } - } else if (config.automerge) { - logger.debug('PR is configured for automerge'); - // skip automerge if there is a new commit since status checks aren't done yet - if (config.ignoreTests === true || !commitSha) { - logger.debug('checking auto-merge'); - const prAutomergeResult = await checkAutoMerge(pr, config); - if (prAutomergeResult?.automerged) { - return { - branchExists, - result: 'automerged', - commitSha, - }; + } else { + if (config.artifactNotices?.length) { + const contentLines: string[] = []; + for (const notice of config.artifactNotices) { + contentLines.push(`##### File name: ${notice.file}`); + contentLines.push(notice.message); } + const content = contentLines.join('\n\n'); + await ensureComment({ + number: pr.number, + topic: artifactNoticeTopic, + content, + }); + } + + if (config.automerge) { + logger.debug('PR is configured for automerge'); + // skip automerge if there is a new commit since status checks aren't done yet + if (config.ignoreTests === true || !commitSha) { + logger.debug('checking auto-merge'); + const prAutomergeResult = await checkAutoMerge(pr, config); + if (prAutomergeResult?.automerged) { + return { + branchExists, + result: 'automerged', + commitSha, + }; + } + } + } else { + logger.debug('PR is not configured for automerge'); } - } else { - logger.debug('PR is not configured for automerge'); } } } catch (err) /* istanbul ignore next */ { diff --git a/lib/workers/repository/update/branch/reuse.spec.ts b/lib/workers/repository/update/branch/reuse.spec.ts index 40856c5cd2aaec..6ebe09a6888f66 100644 --- a/lib/workers/repository/update/branch/reuse.spec.ts +++ b/lib/workers/repository/update/branch/reuse.spec.ts @@ -10,6 +10,7 @@ describe('workers/repository/update/branch/reuse', () => { sourceBranch: 'master', state: 'open', title: 'any', + labels: ['keep-updated'], }; let config: BranchConfig; @@ -189,5 +190,37 @@ describe('workers/repository/update/branch/reuse', () => { const res = await shouldReuseExistingBranch(config); expect(res.reuseExistingBranch).toBeTrue(); }); + + it('returns false if rebaseWhen=never, keepUpdatedLabel and stale', async () => { + config.rebaseWhen = 'never'; + config.keepUpdatedLabel = 'keep-updated'; + platform.getBranchPr.mockResolvedValueOnce(pr); + scm.branchExists.mockResolvedValueOnce(true); + scm.isBranchBehindBase.mockResolvedValueOnce(true); + const res = await shouldReuseExistingBranch(config); + expect(res.reuseExistingBranch).toBeFalse(); + }); + + it('returns false if rebaseWhen=conflicted, keepUpdatedLabel and modified', async () => { + config.rebaseWhen = 'never'; + config.keepUpdatedLabel = 'keep-updated'; + platform.getBranchPr.mockResolvedValue(pr); + scm.branchExists.mockResolvedValueOnce(true); + scm.isBranchConflicted.mockResolvedValueOnce(true); + scm.isBranchModified.mockResolvedValueOnce(false); + const res = await shouldReuseExistingBranch(config); + expect(res.reuseExistingBranch).toBeFalse(); + expect(res.isModified).toBeUndefined(); + }); + + it('returns true if rebaseWhen=never, miss-match keepUpdatedLabel and stale', async () => { + config.rebaseWhen = 'never'; + config.keepUpdatedLabel = 'keep-not-updated'; + platform.getBranchPr.mockResolvedValueOnce(pr); + scm.branchExists.mockResolvedValueOnce(true); + scm.isBranchBehindBase.mockResolvedValueOnce(true); + const res = await shouldReuseExistingBranch(config); + expect(res.reuseExistingBranch).toBeTrue(); + }); }); }); diff --git a/lib/workers/repository/update/branch/reuse.ts b/lib/workers/repository/update/branch/reuse.ts index 0de0afe1e20749..90c79ae5d1f608 100644 --- a/lib/workers/repository/update/branch/reuse.ts +++ b/lib/workers/repository/update/branch/reuse.ts @@ -10,6 +10,28 @@ type ParentBranch = { isConflicted?: boolean; }; +async function shouldKeepUpdated( + config: BranchConfig, + baseBranch: string, + branchName: string, +): Promise { + const keepUpdatedLabel = config.keepUpdatedLabel; + if (!keepUpdatedLabel) { + return false; + } + + const branchPr = await platform.getBranchPr( + config.branchName, + config.baseBranch, + ); + + if (branchPr?.labels?.includes(keepUpdatedLabel)) { + return true; + } + + return false; +} + export async function shouldReuseExistingBranch( config: BranchConfig, ): Promise { @@ -23,6 +45,7 @@ export async function shouldReuseExistingBranch( logger.debug(`Branch already exists`); if ( config.rebaseWhen === 'behind-base-branch' || + (await shouldKeepUpdated(config, baseBranch, branchName)) || (config.rebaseWhen === 'auto' && (config.automerge === true || (await platform.getBranchForceRebase?.(config.baseBranch)))) @@ -53,7 +76,10 @@ export async function shouldReuseExistingBranch( if ((await scm.isBranchModified(branchName)) === false) { logger.debug(`Branch is not mergeable and needs rebasing`); - if (config.rebaseWhen === 'never') { + if ( + config.rebaseWhen === 'never' && + !(await shouldKeepUpdated(config, baseBranch, branchName)) + ) { logger.debug('Rebasing disabled by config'); result.reuseExistingBranch = true; result.isModified = false; diff --git a/lib/workers/repository/update/branch/schedule.ts b/lib/workers/repository/update/branch/schedule.ts index 22c12166779e41..ec159ba74d302c 100644 --- a/lib/workers/repository/update/branch/schedule.ts +++ b/lib/workers/repository/update/branch/schedule.ts @@ -144,7 +144,7 @@ export function isScheduledNow( let configSchedule = config[scheduleKey]; logger.debug( // TODO: types (#22198) - `Checking schedule(${String(configSchedule)}, ${config.timezone!})`, + `Checking schedule(schedule=${String(configSchedule)}, tz=${config.timezone!}, now=${new Date().toISOString()})`, ); if ( !configSchedule || diff --git a/lib/workers/repository/update/branch/status-checks.ts b/lib/workers/repository/update/branch/status-checks.ts index e162818bef8317..16bf3581d7cc59 100644 --- a/lib/workers/repository/update/branch/status-checks.ts +++ b/lib/workers/repository/update/branch/status-checks.ts @@ -4,6 +4,8 @@ import { platform } from '../../../../modules/platform'; import type { BranchStatus } from '../../../../types'; import { isActiveConfidenceLevel } from '../../../../util/merge-confidence'; import type { MergeConfidence } from '../../../../util/merge-confidence/types'; +import { coerceString } from '../../../../util/string'; +import { joinUrlParts } from '../../../../util/url'; export async function resolveBranchStatus( branchName: string, @@ -75,12 +77,18 @@ export async function setStability(config: StabilityConfig): Promise { config.stabilityStatus === 'green' ? 'Updates have met minimum release age requirement' : 'Updates have not met minimum release age requirement'; + + const docsLink = joinUrlParts( + coerceString(config.productLinks?.documentation), + 'configuration-options/#minimumreleaseage', + ); + await setStatusCheck( config.branchName, context, description, config.stabilityStatus, - config.productLinks?.documentation, + docsLink, ); } @@ -110,11 +118,17 @@ export async function setConfidence(config: ConfidenceConfig): Promise { config.confidenceStatus === 'green' ? 'Updates have met Merge Confidence requirement' : 'Updates have not met Merge Confidence requirement'; + + const docsLink = joinUrlParts( + coerceString(config.productLinks?.documentation), + 'merge-confidence', + ); + await setStatusCheck( config.branchName, context, description, config.confidenceStatus, - config.productLinks?.documentation, + docsLink, ); } diff --git a/lib/workers/repository/update/pr/body/updates-table.spec.ts b/lib/workers/repository/update/pr/body/updates-table.spec.ts index 4fd2d140f703f9..54b1c1e7737c2a 100644 --- a/lib/workers/repository/update/pr/body/updates-table.spec.ts +++ b/lib/workers/repository/update/pr/body/updates-table.spec.ts @@ -152,7 +152,7 @@ describe('workers/repository/update/pr/body/updates-table', () => { ); }); - it('selects the best upgrade incase of duplicate table rows', () => { + it('selects the best upgrade in case of duplicate table rows', () => { const upgrade1 = partial({ manager: 'some-manager', branchName: 'some-branch', diff --git a/lib/workers/repository/update/pr/changelog/github/source.ts b/lib/workers/repository/update/pr/changelog/github/source.ts index e1efda0f7350ea..496b5547f60460 100644 --- a/lib/workers/repository/update/pr/changelog/github/source.ts +++ b/lib/workers/repository/update/pr/changelog/github/source.ts @@ -1,6 +1,6 @@ -import URL from 'node:url'; import { GlobalConfig } from '../../../../../../config/global'; import { logger } from '../../../../../../logger'; +import { detectPlatform } from '../../../../../../util/common'; import * as hostRules from '../../../../../../util/host-rules'; import type { BranchUpgradeConfig } from '../../../../../types'; import { ChangeLogSource } from '../source'; @@ -42,8 +42,7 @@ export class GitHubChangeLogSource extends ChangeLogSource { error?: ChangeLogError; } { const sourceUrl = config.sourceUrl!; - const parsedUrl = URL.parse(sourceUrl); - const host = parsedUrl.host; + const host = detectPlatform(sourceUrl); const manager = config.manager; const packageName = config.packageName; @@ -53,10 +52,11 @@ export class GitHubChangeLogSource extends ChangeLogSource { const { token } = hostRules.find({ hostType: 'github', url, + readOnly: true, }); // istanbul ignore if if (host && !token) { - if (host.endsWith('.github.com') || host === 'github.com') { + if (host === 'github') { if (!GlobalConfig.get('githubTokenWarn')) { logger.debug( { manager, packageName, sourceUrl }, diff --git a/lib/workers/repository/update/pr/changelog/release-notes.ts b/lib/workers/repository/update/pr/changelog/release-notes.ts index 79d62f5d0ade94..bdf4a34bd637ff 100644 --- a/lib/workers/repository/update/pr/changelog/release-notes.ts +++ b/lib/workers/repository/update/pr/changelog/release-notes.ts @@ -9,7 +9,7 @@ import { detectPlatform } from '../../../../../util/common'; import { linkify } from '../../../../../util/markdown'; import { newlineRegex, regEx } from '../../../../../util/regex'; import { coerceString } from '../../../../../util/string'; -import { validateUrl } from '../../../../../util/url'; +import { isHttpUrl } from '../../../../../util/url'; import type { BranchUpgradeConfig } from '../../../../types'; import * as bitbucket from './bitbucket'; import * as gitea from './gitea'; @@ -346,12 +346,12 @@ export async function getReleaseNotesMd( .filter(Boolean); let body = section.replace(regEx(/.*?\n(-{3,}\n)?/), '').trim(); for (const word of title) { - if (word.includes(version) && !validateUrl(word)) { + if (word.includes(version) && !isHttpUrl(word)) { logger.trace({ body }, 'Found release notes for v' + version); // TODO: fix url const notesSourceUrl = `${baseUrl}${repository}/blob/HEAD/${changelogFile}`; const mdHeadingLink = title - .filter((word) => !validateUrl(word)) + .filter((word) => !isHttpUrl(word)) .join('-') .replace(regEx(/[^A-Za-z0-9-]/g), ''); const url = `${notesSourceUrl}#${mdHeadingLink}`; diff --git a/lib/workers/repository/update/pr/index.spec.ts b/lib/workers/repository/update/pr/index.spec.ts index d50959fb92ec6b..c0a8f6ac090895 100644 --- a/lib/workers/repository/update/pr/index.spec.ts +++ b/lib/workers/repository/update/pr/index.spec.ts @@ -19,6 +19,7 @@ import type { Pr } from '../../../../modules/platform/types'; import { ExternalHostError } from '../../../../types/errors/external-host-error'; import type { PrCache } from '../../../../util/cache/repository/types'; import { fingerprint } from '../../../../util/fingerprint'; +import { toBase64 } from '../../../../util/string'; import * as _limits from '../../../global/limits'; import type { BranchConfig, BranchUpgradeConfig } from '../../../types'; import { embedChangelogs } from '../../changelog'; @@ -269,6 +270,137 @@ describe('workers/repository/update/pr/index', () => { }); describe('Update', () => { + it('updates PR if labels have changed in config', async () => { + const prDebugData = { + createdInVer: '1.0.0', + targetBranch: 'main', + labels: ['old_label'], + }; + + const existingPr: Pr = { + ...pr, + bodyStruct: getPrBodyStruct( + `\n\n Some body`, + ), + labels: ['old_label'], + }; + platform.getBranchPr.mockResolvedValueOnce(existingPr); + prBody.getPrBody.mockReturnValueOnce( + `\n\n Some body`, + ); + config.labels = ['new_label']; + const res = await ensurePr(config); + + expect(res).toEqual({ + type: 'with-pr', + pr: { + ...pr, + labels: ['old_label'], + bodyStruct: { + hash: expect.any(String), + debugData: { + createdInVer: '1.0.0', + labels: ['new_label'], + targetBranch: 'main', + }, + }, + }, + }); + expect(platform.updatePr).toHaveBeenCalled(); + expect(platform.createPr).not.toHaveBeenCalled(); + expect(logger.logger.debug).toHaveBeenCalledWith( + { + branchName: 'renovate-branch', + prCurrentLabels: ['old_label'], + configuredLabels: ['new_label'], + }, + `PR labels have changed`, + ); + expect(prCache.setPrCache).toHaveBeenCalled(); + }); + + it('skips pr update if existing pr does not have labels in debugData', async () => { + const existingPr: Pr = { + ...pr, + labels: ['old_label'], + }; + platform.getBranchPr.mockResolvedValueOnce(existingPr); + + config.labels = ['new_label']; + const res = await ensurePr(config); + + expect(res).toEqual({ + type: 'with-pr', + pr: { ...pr, labels: ['old_label'] }, + }); + expect(platform.updatePr).not.toHaveBeenCalled(); + expect(platform.createPr).not.toHaveBeenCalled(); + expect(logger.logger.debug).not.toHaveBeenCalledWith( + { + branchName: 'renovate-branch', + oldLabels: ['old_label'], + newLabels: ['new_label'], + }, + `PR labels have changed`, + ); + expect(prCache.setPrCache).toHaveBeenCalled(); + }); + + it('skips pr update if pr labels have been modified by user', async () => { + const prDebugData = { + createdInVer: '1.0.0', + targetBranch: 'main', + labels: ['old_label'], + }; + + const existingPr: Pr = { + ...pr, + bodyStruct: getPrBodyStruct( + `\n\n Some body`, + ), + }; + platform.getBranchPr.mockResolvedValueOnce(existingPr); + + config.labels = ['new_label']; + const res = await ensurePr(config); + + expect(res).toEqual({ + type: 'with-pr', + pr: { + ...pr, + bodyStruct: { + hash: expect.any(String), + debugData: { + createdInVer: '1.0.0', + labels: ['old_label'], + targetBranch: 'main', + }, + }, + }, + }); + expect(platform.updatePr).not.toHaveBeenCalled(); + expect(platform.createPr).not.toHaveBeenCalled(); + expect(logger.logger.debug).not.toHaveBeenCalledWith( + { + branchName: 'renovate-branch', + prCurrentLabels: ['old_label'], + configuredLabels: ['new_label'], + }, + `PR labels have changed`, + ); + expect(logger.logger.debug).toHaveBeenCalledWith( + { prInitialLabels: ['old_label'], prCurrentLabels: [] }, + 'PR labels have been modified by user, skipping labels update', + ); + expect(prCache.setPrCache).toHaveBeenCalled(); + }); + it('updates PR due to title change', async () => { const changedPr: Pr = { ...pr, title: 'Another title' }; // user changed the prTitle platform.getBranchPr.mockResolvedValueOnce(changedPr); diff --git a/lib/workers/repository/update/pr/index.ts b/lib/workers/repository/update/pr/index.ts index bd136d0a3bd91a..e4e11d7738a4ce 100644 --- a/lib/workers/repository/update/pr/index.ts +++ b/lib/workers/repository/update/pr/index.ts @@ -36,7 +36,7 @@ import type { import { embedChangelogs } from '../../changelog'; import { resolveBranchStatus } from '../branch/status-checks'; import { getPrBody } from './body'; -import { prepareLabels } from './labels'; +import { getChangedLabels, prepareLabels, shouldUpdateLabels } from './labels'; import { addParticipants } from './participants'; import { getPrCache, setPrCache } from './pr-cache'; import { @@ -78,15 +78,27 @@ export type EnsurePrResult = ResultWithPr | ResultWithoutPr; export function updatePrDebugData( targetBranch: string, + labels: string[], debugData: PrDebugData | undefined, ): PrDebugData { const createdByRenovateVersion = debugData?.createdInVer ?? pkg.version; const updatedByRenovateVersion = pkg.version; - return { + + const updatedPrDebugData: PrDebugData = { createdInVer: createdByRenovateVersion, updatedInVer: updatedByRenovateVersion, targetBranch, }; + + // Add labels to the debug data object. + // When to add: + // 1. Add it when a new PR is created, i.e., when debugData is undefined. + // 2. Add it if an existing PR already has labels in the debug data, confirming that we can update its labels. + if (!debugData || is.array(debugData.labels)) { + updatedPrDebugData.labels = labels; + } + + return updatedPrDebugData; } function hasNotIgnoredReviewers(pr: Pr, config: BranchConfig): boolean { @@ -319,6 +331,7 @@ export async function ensurePr( { debugData: updatePrDebugData( config.baseBranch, + prepareLabels(config), // include labels in debug data existingPr?.bodyStruct?.debugData, ), }, @@ -344,10 +357,22 @@ export async function ensurePr( const existingPrBodyHash = existingPr.bodyStruct?.hash; const newPrTitle = stripEmojis(prTitle); const newPrBodyHash = hashBody(prBody); + + const prInitialLabels = existingPr.bodyStruct?.debugData?.labels; + const prCurrentLabels = existingPr.labels; + const configuredLabels = prepareLabels(config); + + const labelsNeedUpdate = shouldUpdateLabels( + prInitialLabels, + prCurrentLabels, + configuredLabels, + ); + if ( existingPr?.targetBranch === config.baseBranch && existingPrTitle === newPrTitle && - existingPrBodyHash === newPrBodyHash + existingPrBodyHash === newPrBodyHash && + !labelsNeedUpdate ) { // adds or-cache for existing PRs setPrCache(branchName, prBodyFingerprint, false); @@ -375,6 +400,36 @@ export async function ensurePr( ); updatePrConfig.targetBranch = config.baseBranch; } + + if (labelsNeedUpdate) { + logger.debug( + { + branchName, + prCurrentLabels, + configuredLabels, + }, + 'PR labels have changed', + ); + + // Divide labels into three categories: + // i) addLabels: Labels that need to be added + // ii) removeLabels: Labels that need to be removed + // iii) labels: New labels for the PR, replacing the old labels array entirely. + // This distinction is necessary because different platforms update labels differently + // For more details, refer to the updatePr function of each platform. + + const [addLabels, removeLabels] = getChangedLabels( + prCurrentLabels, + configuredLabels, + ); + + // for Gitea + updatePrConfig.labels = configuredLabels; + + // for GitHub, GitLab + updatePrConfig.addLabels = addLabels; + updatePrConfig.removeLabels = removeLabels; + } if (existingPrTitle !== newPrTitle) { logger.debug( { diff --git a/lib/workers/repository/update/pr/labels.spec.ts b/lib/workers/repository/update/pr/labels.spec.ts index 1c090ba3e96868..7dca6fbc4517ee 100644 --- a/lib/workers/repository/update/pr/labels.spec.ts +++ b/lib/workers/repository/update/pr/labels.spec.ts @@ -1,4 +1,9 @@ -import { prepareLabels } from './labels'; +import { + areLabelsModified, + getChangedLabels, + prepareLabels, + shouldUpdateLabels, +} from './labels'; describe('workers/repository/update/pr/labels', () => { describe('prepareLabels(config)', () => { @@ -78,4 +83,59 @@ describe('workers/repository/update/pr/labels', () => { expect(result).toEqual([]); }); }); + + describe('getChangedLabels', () => { + it('adds new labels', () => { + expect(getChangedLabels(['npm'], ['node', 'npm'])).toEqual([ + ['node'], + [], + ]); + }); + + it('removes old labels', () => { + expect(getChangedLabels(['node', 'npm'], ['npm'])).toEqual([ + [], + ['node'], + ]); + }); + }); + + describe('areLabelsModified', () => { + it('returns true', () => { + expect(areLabelsModified(['npm', 'node'], ['npm'])).toBeTrue(); + }); + + it('returns false', () => { + expect(areLabelsModified(['node', 'npm'], ['node', 'npm'])).toBeFalse(); + expect(areLabelsModified([], [])).toBeFalse(); + }); + }); + + describe('shouldUpdateLabels', () => { + it('returns true', () => { + expect( + shouldUpdateLabels(['npm', 'node'], ['npm', 'node'], ['npm']), + ).toBeTrue(); + expect( + shouldUpdateLabels(['npm', 'node'], ['npm', 'node'], undefined), + ).toBeTrue(); + expect(shouldUpdateLabels([], [], ['npm', 'node'])).toBeTrue(); + }); + + it('returns false if no labels found in debugData', () => { + expect( + shouldUpdateLabels(undefined, ['npm', 'node'], ['npm', 'node']), + ).toBeFalse(); + }); + + it('returns false if labels have been modified by user', () => { + expect(shouldUpdateLabels(['npm', 'node'], ['npm'], ['npm'])).toBeFalse(); + }); + + it('returns false if labels are not changed', () => { + expect( + shouldUpdateLabels(['npm', 'node'], ['npm', 'node'], ['npm', 'node']), + ).toBeFalse(); + }); + }); }); diff --git a/lib/workers/repository/update/pr/labels.ts b/lib/workers/repository/update/pr/labels.ts index df04f801259346..3c41e47b19740f 100644 --- a/lib/workers/repository/update/pr/labels.ts +++ b/lib/workers/repository/update/pr/labels.ts @@ -1,5 +1,7 @@ import is from '@sindresorhus/is'; +import { dequal } from 'dequal'; import type { RenovateConfig } from '../../../../config/types'; +import { logger } from '../../../../logger'; import * as template from '../../../../util/template'; export function prepareLabels(config: RenovateConfig): string[] { @@ -8,5 +10,74 @@ export function prepareLabels(config: RenovateConfig): string[] { return [...new Set([...labels, ...addLabels])] .filter(is.nonEmptyStringAndNotWhitespace) .map((label) => template.compile(label, config)) - .filter(is.nonEmptyStringAndNotWhitespace); + .filter(is.nonEmptyStringAndNotWhitespace) + .sort(); +} + +/** + * Determine changed labels between old and new label arrays. + * + * This function takes two arrays of labels, 'oldLabels' and 'newLabels', and calculates the labels + * that need to be added and removed to transition from 'oldLabels' to 'newLabels'. + */ +export function getChangedLabels( + oldLabels: string[] | undefined, + newLabels: string[] | undefined, +): [string[] | undefined, string[] | undefined] { + const labelsToAdd = newLabels?.filter((l) => !oldLabels?.includes(l)); + const labelsToRemove = oldLabels?.filter((l) => !newLabels?.includes(l)); + + return [labelsToAdd, labelsToRemove]; +} + +/** + * Check if labels in the PR have been modified. + * + * This function compares two arrays of labels, 'prInitialLabels' and 'prCurrentLabels', + * to determine if they are different, indicating that labels in the PR have been modified. + */ +export function areLabelsModified( + prInitialLabels: string[], + prCurrentLabels: string[], +): boolean { + const modified = !dequal(prInitialLabels.sort(), prCurrentLabels.sort()); + + if (modified) { + logger.debug( + { prInitialLabels, prCurrentLabels }, + 'PR labels have been modified by user, skipping labels update', + ); + } + + return modified; +} + +/** + * Determine if labels should be updated in the Pull Request. + */ +export function shouldUpdateLabels( + prInitialLabels: string[] | undefined, + prCurrentLabels: string[] | undefined, + configuredLabels: string[] | undefined, +): boolean { + // If the 'labelsInDebugData' field is undefined + // it means the PR was created before the update-labels logic was merged, and labels should not be updated. + // Reference: https://github.com/renovatebot/renovate/pull/25340 + if (!is.array(prInitialLabels)) { + return false; + } + + // If the labels are unchanged, they should not be updated + if (dequal((configuredLabels ?? []).sort(), prInitialLabels.sort())) { + return false; + } + + // If the labels in the PR have been modified by the user, they should not be updated + if (areLabelsModified(prInitialLabels, prCurrentLabels ?? [])) { + logger.debug('Labels have been modified by user - skipping labels update.'); + return false; + } + + logger.debug('Labels have been changed in repo config- updating labels.'); + return true; } diff --git a/lib/workers/repository/updates/__snapshots__/generate.spec.ts.snap b/lib/workers/repository/updates/__snapshots__/generate.spec.ts.snap index e4a3fb57204d2b..1271188a38f143 100644 --- a/lib/workers/repository/updates/__snapshots__/generate.spec.ts.snap +++ b/lib/workers/repository/updates/__snapshots__/generate.spec.ts.snap @@ -8,6 +8,7 @@ exports[`workers/repository/updates/generate generateBranchConfig() handles @typ "commitMessage": "", "constraints": {}, "depName": "some-dep", + "depTypes": undefined, "dependencyDashboardApproval": false, "dependencyDashboardPrApproval": false, "displayFrom": "", @@ -25,12 +26,12 @@ exports[`workers/repository/updates/generate generateBranchConfig() handles @typ "prettyDepType": "dependency", "recreateClosed": false, "releaseTimestamp": undefined, - "reuseLockFiles": true, "upgrades": [ { "branchName": "some-branch", "commitMessage": "", "depName": "some-dep", + "depTypes": undefined, "displayFrom": "", "displayPending": "", "displayTo": "0.6.0", @@ -50,6 +51,7 @@ exports[`workers/repository/updates/generate generateBranchConfig() handles @typ "commitMessage": "", "datasource": "npm", "depName": "some-dep", + "depTypes": undefined, "displayFrom": "", "displayPending": "", "displayTo": "1.0.0", @@ -67,6 +69,7 @@ exports[`workers/repository/updates/generate generateBranchConfig() handles @typ "branchName": "some-branch", "commitMessage": "", "depName": "@types/some-dep", + "depTypes": undefined, "displayFrom": "", "displayPending": "", "displayTo": "0.5.7", @@ -93,6 +96,7 @@ exports[`workers/repository/updates/generate generateBranchConfig() handles @typ "constraints": {}, "datasource": "npm", "depName": "some-dep", + "depTypes": undefined, "dependencyDashboardApproval": false, "dependencyDashboardPrApproval": false, "displayFrom": "", @@ -108,7 +112,6 @@ exports[`workers/repository/updates/generate generateBranchConfig() handles @typ "prettyDepType": "dependency", "recreateClosed": false, "releaseTimestamp": undefined, - "reuseLockFiles": true, "upgrades": [ { "branchName": "some-branch", @@ -116,6 +119,7 @@ exports[`workers/repository/updates/generate generateBranchConfig() handles @typ "commitMessage": "", "datasource": "npm", "depName": "some-dep", + "depTypes": undefined, "displayFrom": "", "displayPending": "", "displayTo": "0.6.0", @@ -132,6 +136,7 @@ exports[`workers/repository/updates/generate generateBranchConfig() handles @typ "commitMessage": "", "datasource": "npm", "depName": "some-dep", + "depTypes": undefined, "displayFrom": "", "displayPending": "", "displayTo": "1.0.0", @@ -150,6 +155,7 @@ exports[`workers/repository/updates/generate generateBranchConfig() handles @typ "currentVersion": "0.5.7", "datasource": "npm", "depName": "@types/some-dep", + "depTypes": undefined, "displayFrom": "0.5.7", "displayPending": "", "displayTo": "0.5.8", @@ -173,6 +179,7 @@ exports[`workers/repository/updates/generate generateBranchConfig() handles lock "branchName": "some-branch", "commitMessage": "", "constraints": {}, + "depTypes": undefined, "dependencyDashboardApproval": false, "dependencyDashboardPrApproval": false, "displayFrom": "", @@ -186,11 +193,11 @@ exports[`workers/repository/updates/generate generateBranchConfig() handles lock "prettyDepType": "dependency", "recreateClosed": true, "releaseTimestamp": undefined, - "reuseLockFiles": true, "upgrades": [ { "branchName": "some-branch", "commitMessage": "", + "depTypes": undefined, "displayFrom": "", "displayPending": "", "displayTo": "", @@ -213,6 +220,7 @@ exports[`workers/repository/updates/generate generateBranchConfig() handles lock "constraints": {}, "currentValue": "^1.0.0", "currentVersion": "1.0.0", + "depTypes": undefined, "dependencyDashboardApproval": false, "dependencyDashboardPrApproval": false, "displayFrom": "1.0.0", @@ -231,13 +239,13 @@ exports[`workers/repository/updates/generate generateBranchConfig() handles lock "prettyNewVersion": "v1.0.1", "recreateClosed": false, "releaseTimestamp": undefined, - "reuseLockFiles": true, "upgrades": [ { "branchName": "some-branch", "commitMessage": "", "currentValue": "^1.0.0", "currentVersion": "1.0.0", + "depTypes": undefined, "displayFrom": "1.0.0", "displayPending": "", "displayTo": "1.0.1", diff --git a/lib/workers/repository/updates/branch-name.spec.ts b/lib/workers/repository/updates/branch-name.spec.ts index c78652419cc6c2..297d7012bcd4af 100644 --- a/lib/workers/repository/updates/branch-name.spec.ts +++ b/lib/workers/repository/updates/branch-name.spec.ts @@ -31,14 +31,15 @@ describe('workers/repository/updates/branch-name', () => { it('uses groupSlug if defined', () => { const upgrade: RenovateConfig = { groupName: 'some group name', - groupSlug: 'some group slug', + groupSlug: 'some group {{parentDir}}', + parentDir: 'abc', group: { branchName: '{{groupSlug}}-{{branchTopic}}', branchTopic: 'grouptopic', }, }; generateBranchName(upgrade); - expect(upgrade.branchName).toBe('some-group-slug-grouptopic'); + expect(upgrade.branchName).toBe('some-group-abc-grouptopic'); }); it('separates major with groups', () => { @@ -58,6 +59,43 @@ describe('workers/repository/updates/branch-name', () => { expect(upgrade.branchName).toBe('major-2-some-group-slug-grouptopic'); }); + it('separates minor with groups', () => { + const upgrade: RenovateConfig = { + groupName: 'some group name', + groupSlug: 'some group slug', + updateType: 'minor', + separateMultipleMinor: true, + newMinor: 1, + newMajor: 2, + group: { + branchName: '{{groupSlug}}-{{branchTopic}}', + branchTopic: 'grouptopic', + }, + }; + generateBranchName(upgrade); + expect(upgrade.branchName).toBe('minor-2.1-some-group-slug-grouptopic'); + }); + + it('separates minor when separateMultipleMinor=true', () => { + const upgrade: RenovateConfig = { + branchName: + '{{{branchPrefix}}}{{{additionalBranchPrefix}}}{{{branchTopic}}}', + branchPrefix: 'renovate/', + additionalBranchPrefix: '', + depNameSanitized: 'lodash', + newMajor: 4, + separateMinorPatch: true, + isPatch: true, + newMinor: 17, + branchTopic: + '{{{depNameSanitized}}}-{{{newMajor}}}{{#if separateMinorPatch}}{{#if isPatch}}.{{{newMinor}}}{{/if}}{{/if}}.x{{#if isLockfileUpdate}}-lockfile{{/if}}', + depName: 'dep', + group: {}, + }; + generateBranchName(upgrade); + expect(upgrade.branchName).toBe('renovate/lodash-4.17.x'); + }); + it('uses single major with groups', () => { const upgrade: RenovateConfig = { groupName: 'some group name', diff --git a/lib/workers/repository/updates/branch-name.ts b/lib/workers/repository/updates/branch-name.ts index 9036a161c07fe1..bc42966fd0ded7 100644 --- a/lib/workers/repository/updates/branch-name.ts +++ b/lib/workers/repository/updates/branch-name.ts @@ -47,6 +47,8 @@ function cleanBranchName( export function generateBranchName(update: RenovateConfig): void { // Check whether to use a group name + const newMajor = String(update.newMajor); + const newMinor = String(update.newMinor); if (update.groupName) { update.groupName = template.compile(update.groupName, update); logger.trace('Using group branchName template'); @@ -54,17 +56,24 @@ export function generateBranchName(update: RenovateConfig): void { logger.trace( `Dependency ${update.depName!} is part of group ${update.groupName}`, ); - update.groupSlug = slugify(update.groupSlug ?? update.groupName, { + if (update.groupSlug) { + update.groupSlug = template.compile(update.groupSlug, update); + } else { + update.groupSlug = update.groupName; + } + update.groupSlug = slugify(update.groupSlug, { lower: true, }); if (update.updateType === 'major' && update.separateMajorMinor) { if (update.separateMultipleMajor) { - const newMajor = String(update.newMajor); update.groupSlug = `major-${newMajor}-${update.groupSlug}`; } else { update.groupSlug = `major-${update.groupSlug}`; } } + if (update.updateType === 'minor' && update.separateMultipleMinor) { + update.groupSlug = `minor-${newMajor}.${newMinor}-${update.groupSlug}`; + } if (update.updateType === 'patch' && update.separateMinorPatch) { update.groupSlug = `patch-${update.groupSlug}`; } @@ -111,7 +120,9 @@ export function generateBranchName(update: RenovateConfig): void { update.branchName = template.compile(update.branchName, update); update.branchName = template.compile(update.branchName, update); } - + if (update.updateType === 'minor' && update.separateMultipleMinor) { + update.branchName = update.branchName.replace('.x', `.${newMinor}.x`); + } update.branchName = cleanBranchName( update.branchName, update.branchNameStrict, diff --git a/lib/workers/repository/updates/generate.spec.ts b/lib/workers/repository/updates/generate.spec.ts index db94dcbf3c7531..f84c767c7be3e2 100644 --- a/lib/workers/repository/updates/generate.spec.ts +++ b/lib/workers/repository/updates/generate.spec.ts @@ -97,7 +97,6 @@ describe('workers/repository/updates/generate', () => { lockedVersion: '1.0.0', newValue: '^1.0.0', newVersion: '1.0.1', - reuseLockFiles: true, prettyNewVersion: 'v1.0.1', upgrades: [ { @@ -1443,5 +1442,60 @@ describe('workers/repository/updates/generate', () => { const res = generateBranchConfig(upgrades); expect(res.additionalReviewers).toEqual(['foo', 'bar']); }); + + it('merges depTypes', () => { + const upgrades = [ + { + ...requiredDefaultOptions, + branchName: 'some-branch', + manager: 'some-manager', + depType: 'devDependencies', + }, + { + ...requiredDefaultOptions, + branchName: 'some-branch', + manager: 'some-manager', + depType: 'dependencies', + }, + { + ...requiredDefaultOptions, + branchName: 'some-branch', + manager: 'some-manager', + depType: 'devDependencies', + }, + ] satisfies BranchUpgradeConfig[]; + const res = generateBranchConfig(upgrades); + expect(res.depTypes).toEqual(['dependencies', 'devDependencies']); + }); + + it('depTypes is available on each branch upgrade object', () => { + const upgrades = [ + { + ...requiredDefaultOptions, + branchName: 'some-branch', + manager: 'some-manager', + depType: 'devDependencies', + }, + { + ...requiredDefaultOptions, + branchName: 'some-branch', + manager: 'some-manager', + depType: 'dependencies', + }, + { + ...requiredDefaultOptions, + branchName: 'some-branch', + manager: 'some-manager', + depType: 'devDependencies', + }, + ] satisfies BranchUpgradeConfig[]; + const res = generateBranchConfig(upgrades); + + expect(res.depTypes).toEqual(['dependencies', 'devDependencies']); + + for (const upgrade of res.upgrades) { + expect(upgrade.depTypes).toEqual(res.depTypes); + } + }); }); }); diff --git a/lib/workers/repository/updates/generate.ts b/lib/workers/repository/updates/generate.ts index d8ba25c27a5305..81ea7801975905 100644 --- a/lib/workers/repository/updates/generate.ts +++ b/lib/workers/repository/updates/generate.ts @@ -83,6 +83,7 @@ export function generateBranchConfig( const newValue: string[] = []; const toVersions: string[] = []; const toValues = new Set(); + const depTypes = new Set(); for (const upg of branchUpgrades) { upg.recreateClosed = upg.recreateWhen === 'always'; @@ -119,6 +120,9 @@ export function generateBranchConfig( toVersions.push(upg.newVersion!); } toValues.add(upg.newValue!); + if (upg.depType) { + depTypes.add(upg.depType); + } // prettify newVersion and newMajor for printing if (upg.newVersion) { upg.prettyNewVersion = prettifyVersion(upg.newVersion); @@ -144,9 +148,16 @@ export function generateBranchConfig( const useGroupSettings = hasGroupName && groupEligible; logger.trace(`useGroupSettings: ${useGroupSettings}`); let releaseTimestamp: string; + + if (depTypes.size) { + config.depTypes = Array.from(depTypes).sort(); + } + for (const branchUpgrade of branchUpgrades) { let upgrade: BranchUpgradeConfig = { ...branchUpgrade }; + upgrade.depTypes = config.depTypes; + // needs to be done for each upgrade, as we reorder them below if (newValue.length > 1 && !groupEligible) { upgrade.commitMessageExtra = `to v${toVersions[0]}`; @@ -334,9 +345,6 @@ export function generateBranchConfig( ...config.upgrades[0], releaseTimestamp: releaseTimestamp!, }; // TODO: fixme (#9666) - config.reuseLockFiles = config.upgrades.every( - (upgrade) => upgrade.updateType !== 'lockFileMaintenance', - ); config.dependencyDashboardApproval = config.upgrades.some( (upgrade) => upgrade.dependencyDashboardApproval, ); diff --git a/lib/workers/types.ts b/lib/workers/types.ts index df3916fb859678..e4c6477b45f662 100644 --- a/lib/workers/types.ts +++ b/lib/workers/types.ts @@ -10,6 +10,7 @@ import type { import type { Release } from '../modules/datasource/types'; import type { ArtifactError, + ArtifactNotice, ExtractConfig, LookupUpdate, PackageDependency, @@ -30,6 +31,7 @@ export interface BranchUpgradeConfig Partial, RenovateSharedConfig { artifactErrors?: ArtifactError[]; + artifactNotices?: ArtifactNotice[]; autoReplaceStringTemplate?: string; baseDeps?: PackageDependency[]; branchName: string; @@ -40,6 +42,7 @@ export interface BranchUpgradeConfig currentDigestShort?: string; currentValue?: string; depIndex?: number; + depTypes?: string[]; displayPending?: string; excludeCommitPaths?: string[]; diff --git a/license b/license index 07ca9fa9683400..0ad25db4bd1d86 100644 --- a/license +++ b/license @@ -1,11 +1,3 @@ -Renovate versions 12.0.0 (released 2018-04-09) and onwards are released -under the GNU Affero General Public License. - -Renovate versions 11 and earlier were released under the MIT license -and therefore the MIT notice is retained in this file for that code only. - -Both licenses are included below for reference: - GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 @@ -641,8 +633,8 @@ the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, @@ -667,26 +659,3 @@ specific requirements. if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see . - -MIT -- for code released prior to v12.0.0 ---- - -Copyright (c) 2017 Rhys Arkins (rhys.arkins.net) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/package.json b/package.json index 64882e20af2eab..d148d62709a14b 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,7 @@ "generate": "run-s 'generate:*'", "generate:imports": "node tools/generate-imports.mjs", "git-check": "node tools/check-git-version.mjs", - "jest": "node tools/jest.mjs", - "jest:vscode": "node tools/jest.mjs", - "jest-debug": "NODE_OPTIONS='$NODE_OPTIONS --inspect-brk' node tools/jest.mjs --testTimeout=100000000", + "jest": "GIT_ALLOW_PROTOCOL=file LOG_LEVEL=fatal node --experimental-vm-modules node_modules/jest/bin/jest.js --logHeapUsage", "lint": "run-s ls-lint type-check eslint prettier markdown-lint git-check doc-fence-check", "lint-fix": "run-s eslint-fix prettier-fix markdown-lint-fix", "ls-lint": "ls-lint", @@ -53,6 +51,7 @@ "test-e2e:install": "cd test/e2e && npm install --no-package-lock --prod", "test-e2e:run": "cd test/e2e && npm test", "test-schema": "run-s create-json-schema", + "test:docs": "node --test tools/docs/test/", "schedule-test-shards": "SCHEDULE_TEST_SHARDS=true ts-node jest.config.ts", "tsc": "tsc", "type-check": "run-s 'generate:*' 'tsc --noEmit {@}' --", @@ -136,49 +135,46 @@ "homepage": "https://renovatebot.com", "engines": { "node": "^18.12.0 || >=20.0.0", - "pnpm": "^8.0.0" + "pnpm": "^9.0.0" }, "engines-next": { "description": "Versions other than the below are deprecated and a warning will be logged", "node": "^18.12.0 || >=20.0.0" }, "dependencies": { - "@aws-sdk/client-codecommit": "3.363.0", - "@aws-sdk/client-ec2": "3.363.0", - "@aws-sdk/client-ecr": "3.363.0", - "@aws-sdk/client-rds": "3.363.0", - "@aws-sdk/client-s3": "3.363.0", - "@aws-sdk/credential-providers": "3.363.0", + "@aws-sdk/client-codecommit": "3.565.0", + "@aws-sdk/client-ec2": "3.565.0", + "@aws-sdk/client-ecr": "3.565.0", + "@aws-sdk/client-rds": "3.565.0", + "@aws-sdk/client-s3": "3.565.0", + "@aws-sdk/credential-providers": "3.565.0", "@breejs/later": "4.2.0", - "@cdktf/hcl2json": "0.20.3", - "@opentelemetry/api": "1.7.0", - "@opentelemetry/context-async-hooks": "1.21.0", - "@opentelemetry/exporter-trace-otlp-http": "0.48.0", - "@opentelemetry/instrumentation": "0.48.0", - "@opentelemetry/instrumentation-bunyan": "0.35.0", - "@opentelemetry/instrumentation-http": "0.48.0", - "@opentelemetry/resources": "1.21.0", - "@opentelemetry/sdk-trace-base": "1.21.0", - "@opentelemetry/sdk-trace-node": "1.21.0", - "@opentelemetry/semantic-conventions": "1.21.0", + "@cdktf/hcl2json": "0.20.7", + "@opentelemetry/api": "1.8.0", + "@opentelemetry/context-async-hooks": "1.24.1", + "@opentelemetry/exporter-trace-otlp-http": "0.51.1", + "@opentelemetry/instrumentation": "0.51.1", + "@opentelemetry/instrumentation-bunyan": "0.38.0", + "@opentelemetry/instrumentation-http": "0.51.1", + "@opentelemetry/resources": "1.24.1", + "@opentelemetry/sdk-trace-base": "1.24.1", + "@opentelemetry/sdk-trace-node": "1.24.1", + "@opentelemetry/semantic-conventions": "1.24.1", "@qnighy/marshal": "0.1.3", - "@renovatebot/osv-offline": "1.5.1", - "@renovatebot/pep440": "3.0.19", + "@renovatebot/kbpgp": "3.0.1", + "@renovatebot/osv-offline": "1.5.5", + "@renovatebot/pep440": "3.0.20", "@renovatebot/ruby-semver": "3.0.23", "@sindresorhus/is": "4.6.0", - "@types/better-sqlite3": "7.6.9", - "@types/ms": "0.7.34", - "@types/tmp": "0.2.6", - "@yarnpkg/core": "4.0.3", - "@yarnpkg/parsers": "3.0.0", + "@yarnpkg/core": "4.0.5", + "@yarnpkg/parsers": "3.0.2", "agentkeepalive": "4.5.0", "aggregate-error": "3.1.0", "auth-header": "1.0.0", "aws4": "1.12.0", - "azure-devops-node-api": "12.4.0", - "better-sqlite3": "9.4.3", + "azure-devops-node-api": "13.0.0", "bunyan": "1.8.15", - "cacache": "18.0.2", + "cacache": "18.0.3", "cacheable-lookup": "5.0.4", "chalk": "4.1.2", "changelog-filename-regex": "2.0.1", @@ -200,31 +196,31 @@ "fs-extra": "11.2.0", "git-url-parse": "14.0.0", "github-url-from-git": "1.5.0", - "glob": "10.3.10", + "glob": "10.3.15", "global-agent": "3.0.0", "good-enough-parser": "1.1.23", - "google-auth-library": "9.6.3", + "google-auth-library": "9.10.0", "got": "11.8.6", "graph-data-structure": "3.5.0", "handlebars": "4.7.8", "ignore": "5.3.1", - "ini": "4.1.1", + "ini": "4.1.2", "js-yaml": "4.1.0", "json-dup-key-validator": "1.0.3", "json-stringify-pretty-compact": "3.0.0", "json5": "2.2.3", - "jsonata": "2.0.3", + "jsonata": "2.0.5", + "jsonc-parser": "3.2.1", "klona": "2.0.6", - "lru-cache": "10.2.0", + "lru-cache": "10.2.2", "luxon": "3.4.4", - "markdown-it": "13.0.2", + "markdown-it": "14.1.0", "markdown-table": "2.0.0", - "minimatch": "9.0.3", + "minimatch": "9.0.4", "moo": "0.5.2", "ms": "2.1.3", "nanoid": "3.3.7", - "node-html-parser": "6.1.12", - "openpgp": "5.11.1", + "node-html-parser": "6.1.13", "p-all": "3.0.0", "p-map": "4.0.0", "p-queue": "6.6.2", @@ -235,25 +231,27 @@ "remark": "13.0.0", "remark-github": "10.1.0", "safe-stable-stringify": "2.4.3", - "semver": "7.5.4", + "semver": "7.6.2", "semver-stable": "3.0.0", "semver-utils": "1.1.4", "shlex": "2.1.2", - "simple-git": "3.22.0", + "simple-git": "3.24.0", "slugify": "1.6.6", "source-map-support": "0.5.21", "toml-eslint-parser": "0.9.3", - "traverse": "0.6.8", + "traverse": "0.6.9", "tslib": "2.6.2", "upath": "2.0.1", "url-join": "4.0.1", - "validate-npm-package-name": "5.0.0", + "validate-npm-package-name": "5.0.1", "vuln-vects": "1.1.0", "xmldoc": "1.3.0", - "zod": "3.22.4" + "zod": "3.23.8" }, "optionalDependencies": { - "re2": "1.20.9" + "better-sqlite3": "9.6.0", + "openpgp": "5.11.1", + "re2": "1.20.11" }, "devDependencies": { "@hyrious/marshal": "0.3.3", @@ -262,13 +260,14 @@ "@jest/reporters": "29.7.0", "@jest/test-result": "29.7.0", "@jest/types": "29.6.3", - "@ls-lint/ls-lint": "2.2.2", + "@ls-lint/ls-lint": "2.2.3", "@openpgp/web-stream-tools": "0.0.14", "@renovate/eslint-plugin": "file:tools/eslint", "@semantic-release/exec": "6.0.3", - "@swc/core": "1.4.2", + "@swc/core": "1.5.7", "@types/auth-header": "1.0.6", "@types/aws4": "1.11.6", + "@types/better-sqlite3": "7.6.10", "@types/breejs__later": "4.1.5", "@types/bunyan": "1.8.11", "@types/cacache": "17.0.2", @@ -277,46 +276,46 @@ "@types/clean-git-ref": "2.0.2", "@types/common-tags": "1.8.4", "@types/conventional-commits-detector": "1.0.2", - "@types/diff": "5.0.9", - "@types/eslint": "8.56.3", + "@types/diff": "5.2.1", + "@types/eslint": "8.56.10", "@types/fs-extra": "11.0.4", "@types/git-url-parse": "9.0.3", "@types/github-url-from-git": "1.5.3", "@types/global-agent": "2.1.3", - "@types/ini": "1.3.34", + "@types/ini": "4.1.0", "@types/js-yaml": "4.0.9", "@types/json-dup-key-validator": "1.0.2", "@types/linkify-markdown": "1.0.3", - "@types/lodash": "4.14.202", + "@types/lodash": "4.17.1", "@types/luxon": "3.4.2", - "@types/markdown-it": "13.0.7", + "@types/markdown-it": "14.0.1", "@types/markdown-table": "2.0.0", "@types/marshal": "0.5.3", "@types/mdast": "3.0.15", "@types/moo": "0.5.9", - "@types/nock": "10.0.3", - "@types/node": "18.19.21", + "@types/ms": "0.7.34", + "@types/node": "18.19.33", "@types/parse-link-header": "2.0.3", - "@types/prettier": "2.7.3", - "@types/semver": "7.5.6", + "@types/semver": "7.5.8", "@types/semver-stable": "3.0.2", "@types/semver-utils": "1.1.3", - "@types/tar": "6.1.11", + "@types/tar": "6.1.13", + "@types/tmp": "0.2.6", "@types/traverse": "0.6.36", + "@types/unist": "2.0.10", "@types/url-join": "4.0.3", "@types/validate-npm-package-name": "4.0.2", "@types/xmldoc": "1.1.9", - "@typescript-eslint/eslint-plugin": "6.21.0", - "@typescript-eslint/parser": "6.21.0", - "aws-sdk-client-mock": "3.0.1", + "@typescript-eslint/eslint-plugin": "7.10.0", + "@typescript-eslint/parser": "7.10.0", + "aws-sdk-client-mock": "4.0.0", "callsite": "1.0.0", "common-tags": "1.8.2", "conventional-changelog-conventionalcommits": "7.0.2", "diff": "5.2.0", "emojibase-data": "15.3.0", - "eslint": "8.56.0", - "eslint-config-prettier": "9.1.0", - "eslint-formatter-gha": "1.4.3", + "eslint": "8.57.0", + "eslint-formatter-gha": "1.5.0", "eslint-import-resolver-typescript": "3.6.1", "eslint-plugin-import": "2.29.1", "eslint-plugin-jest": "27.9.0", @@ -330,27 +329,35 @@ "jest": "29.7.0", "jest-extended": "4.0.2", "jest-mock": "29.7.0", - "jest-mock-extended": "3.0.5", + "jest-mock-extended": "3.0.7", "jest-snapshot": "29.7.0", - "markdownlint-cli2": "0.12.1", - "memfs": "4.7.7", - "nock": "13.5.3", + "markdownlint-cli2": "0.13.0", + "memfs": "4.9.2", + "nock": "13.5.4", "npm-run-all2": "6.1.2", "nyc": "15.1.0", "pretty-format": "29.7.0", - "rimraf": "5.0.5", + "rimraf": "5.0.7", "semantic-release": "22.0.12", - "tar": "6.2.0", + "tar": "6.2.1", "tmp-promise": "3.0.3", "ts-jest": "29.1.2", "ts-node": "10.9.2", - "type-fest": "4.10.3", - "typescript": "5.3.3", + "type-fest": "4.18.2", + "typescript": "5.4.5", "unified": "9.2.2" }, - "packageManager": "pnpm@8.15.3", + "packageManager": "pnpm@9.1.1", "files": [ "dist", "renovate-schema.json" - ] + ], + "pnpm": { + "neverBuiltDependencies": [ + "dtrace-provider" + ], + "overrides": { + "@types/linkify-it": "<5.0.0" + } + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 619a8d8826cc65..fe3862425bb390 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,97 +1,94 @@ -lockfileVersion: '6.0' +lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + '@types/linkify-it': <5.0.0 + importers: .: dependencies: '@aws-sdk/client-codecommit': - specifier: 3.363.0 - version: 3.363.0 + specifier: 3.565.0 + version: 3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)) '@aws-sdk/client-ec2': - specifier: 3.363.0 - version: 3.363.0 + specifier: 3.565.0 + version: 3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)) '@aws-sdk/client-ecr': - specifier: 3.363.0 - version: 3.363.0 + specifier: 3.565.0 + version: 3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)) '@aws-sdk/client-rds': - specifier: 3.363.0 - version: 3.363.0 + specifier: 3.565.0 + version: 3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)) '@aws-sdk/client-s3': - specifier: 3.363.0 - version: 3.363.0 + specifier: 3.565.0 + version: 3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)) '@aws-sdk/credential-providers': - specifier: 3.363.0 - version: 3.363.0 + specifier: 3.565.0 + version: 3.565.0(@aws-sdk/client-sso-oidc@3.569.0) '@breejs/later': specifier: 4.2.0 version: 4.2.0 '@cdktf/hcl2json': - specifier: 0.20.3 - version: 0.20.3 + specifier: 0.20.7 + version: 0.20.7 '@opentelemetry/api': - specifier: 1.7.0 - version: 1.7.0 + specifier: 1.8.0 + version: 1.8.0 '@opentelemetry/context-async-hooks': - specifier: 1.21.0 - version: 1.21.0(@opentelemetry/api@1.7.0) + specifier: 1.24.1 + version: 1.24.1(@opentelemetry/api@1.8.0) '@opentelemetry/exporter-trace-otlp-http': - specifier: 0.48.0 - version: 0.48.0(@opentelemetry/api@1.7.0) + specifier: 0.51.1 + version: 0.51.1(@opentelemetry/api@1.8.0) '@opentelemetry/instrumentation': - specifier: 0.48.0 - version: 0.48.0(@opentelemetry/api@1.7.0) + specifier: 0.51.1 + version: 0.51.1(@opentelemetry/api@1.8.0) '@opentelemetry/instrumentation-bunyan': - specifier: 0.35.0 - version: 0.35.0(@opentelemetry/api@1.7.0) + specifier: 0.38.0 + version: 0.38.0(@opentelemetry/api@1.8.0) '@opentelemetry/instrumentation-http': - specifier: 0.48.0 - version: 0.48.0(@opentelemetry/api@1.7.0) + specifier: 0.51.1 + version: 0.51.1(@opentelemetry/api@1.8.0) '@opentelemetry/resources': - specifier: 1.21.0 - version: 1.21.0(@opentelemetry/api@1.7.0) + specifier: 1.24.1 + version: 1.24.1(@opentelemetry/api@1.8.0) '@opentelemetry/sdk-trace-base': - specifier: 1.21.0 - version: 1.21.0(@opentelemetry/api@1.7.0) + specifier: 1.24.1 + version: 1.24.1(@opentelemetry/api@1.8.0) '@opentelemetry/sdk-trace-node': - specifier: 1.21.0 - version: 1.21.0(@opentelemetry/api@1.7.0) + specifier: 1.24.1 + version: 1.24.1(@opentelemetry/api@1.8.0) '@opentelemetry/semantic-conventions': - specifier: 1.21.0 - version: 1.21.0 + specifier: 1.24.1 + version: 1.24.1 '@qnighy/marshal': specifier: 0.1.3 version: 0.1.3 + '@renovatebot/kbpgp': + specifier: 3.0.1 + version: 3.0.1 '@renovatebot/osv-offline': - specifier: 1.5.1 - version: 1.5.1 + specifier: 1.5.5 + version: 1.5.5(encoding@0.1.13) '@renovatebot/pep440': - specifier: 3.0.19 - version: 3.0.19 + specifier: 3.0.20 + version: 3.0.20 '@renovatebot/ruby-semver': specifier: 3.0.23 version: 3.0.23 '@sindresorhus/is': specifier: 4.6.0 version: 4.6.0 - '@types/better-sqlite3': - specifier: 7.6.9 - version: 7.6.9 - '@types/ms': - specifier: 0.7.34 - version: 0.7.34 - '@types/tmp': - specifier: 0.2.6 - version: 0.2.6 '@yarnpkg/core': - specifier: 4.0.3 - version: 4.0.3(typanion@3.14.0) + specifier: 4.0.5 + version: 4.0.5(typanion@3.14.0) '@yarnpkg/parsers': - specifier: 3.0.0 - version: 3.0.0 + specifier: 3.0.2 + version: 3.0.2 agentkeepalive: specifier: 4.5.0 version: 4.5.0 @@ -105,17 +102,14 @@ importers: specifier: 1.12.0 version: 1.12.0 azure-devops-node-api: - specifier: 12.4.0 - version: 12.4.0 - better-sqlite3: - specifier: 9.4.3 - version: 9.4.3 + specifier: 13.0.0 + version: 13.0.0 bunyan: specifier: 1.8.15 version: 1.8.15 cacache: - specifier: 18.0.2 - version: 18.0.2 + specifier: 18.0.3 + version: 18.0.3 cacheable-lookup: specifier: 5.0.4 version: 5.0.4 @@ -180,8 +174,8 @@ importers: specifier: 1.5.0 version: 1.5.0 glob: - specifier: 10.3.10 - version: 10.3.10 + specifier: 10.3.15 + version: 10.3.15 global-agent: specifier: 3.0.0 version: 3.0.0 @@ -189,8 +183,8 @@ importers: specifier: 1.1.23 version: 1.1.23 google-auth-library: - specifier: 9.6.3 - version: 9.6.3 + specifier: 9.10.0 + version: 9.10.0(encoding@0.1.13) got: specifier: 11.8.6 version: 11.8.6 @@ -204,8 +198,8 @@ importers: specifier: 5.3.1 version: 5.3.1 ini: - specifier: 4.1.1 - version: 4.1.1 + specifier: 4.1.2 + version: 4.1.2 js-yaml: specifier: 4.1.0 version: 4.1.0 @@ -219,26 +213,29 @@ importers: specifier: 2.2.3 version: 2.2.3 jsonata: - specifier: 2.0.3 - version: 2.0.3 + specifier: 2.0.5 + version: 2.0.5 + jsonc-parser: + specifier: 3.2.1 + version: 3.2.1 klona: specifier: 2.0.6 version: 2.0.6 lru-cache: - specifier: 10.2.0 - version: 10.2.0 + specifier: 10.2.2 + version: 10.2.2 luxon: specifier: 3.4.4 version: 3.4.4 markdown-it: - specifier: 13.0.2 - version: 13.0.2 + specifier: 14.1.0 + version: 14.1.0 markdown-table: specifier: 2.0.0 version: 2.0.0 minimatch: - specifier: 9.0.3 - version: 9.0.3 + specifier: 9.0.4 + version: 9.0.4 moo: specifier: 0.5.2 version: 0.5.2 @@ -249,11 +246,8 @@ importers: specifier: 3.3.7 version: 3.3.7 node-html-parser: - specifier: 6.1.12 - version: 6.1.12 - openpgp: - specifier: 5.11.1 - version: 5.11.1 + specifier: 6.1.13 + version: 6.1.13 p-all: specifier: 3.0.0 version: 3.0.0 @@ -285,8 +279,8 @@ importers: specifier: 2.4.3 version: 2.4.3 semver: - specifier: 7.5.4 - version: 7.5.4 + specifier: 7.6.2 + version: 7.6.2 semver-stable: specifier: 3.0.0 version: 3.0.0 @@ -297,8 +291,8 @@ importers: specifier: 2.1.2 version: 2.1.2 simple-git: - specifier: 3.22.0 - version: 3.22.0 + specifier: 3.24.0 + version: 3.24.0 slugify: specifier: 1.6.6 version: 1.6.6 @@ -309,8 +303,8 @@ importers: specifier: 0.9.3 version: 0.9.3 traverse: - specifier: 0.6.8 - version: 0.6.8 + specifier: 0.6.9 + version: 0.6.9 tslib: specifier: 2.6.2 version: 2.6.2 @@ -321,8 +315,8 @@ importers: specifier: 4.0.1 version: 4.0.1 validate-npm-package-name: - specifier: 5.0.0 - version: 5.0.0 + specifier: 5.0.1 + version: 5.0.1 vuln-vects: specifier: 1.1.0 version: 1.1.0 @@ -330,12 +324,18 @@ importers: specifier: 1.3.0 version: 1.3.0 zod: - specifier: 3.22.4 - version: 3.22.4 + specifier: 3.23.8 + version: 3.23.8 optionalDependencies: + better-sqlite3: + specifier: 9.6.0 + version: 9.6.0 + openpgp: + specifier: 5.11.1 + version: 5.11.1 re2: - specifier: 1.20.9 - version: 1.20.9 + specifier: 1.20.11 + version: 1.20.11 devDependencies: '@hyrious/marshal': specifier: 0.3.3 @@ -356,26 +356,29 @@ importers: specifier: 29.6.3 version: 29.6.3 '@ls-lint/ls-lint': - specifier: 2.2.2 - version: 2.2.2 + specifier: 2.2.3 + version: 2.2.3 '@openpgp/web-stream-tools': specifier: 0.0.14 - version: 0.0.14(typescript@5.3.3) + version: 0.0.14(typescript@5.4.5) '@renovate/eslint-plugin': specifier: file:tools/eslint - version: file:tools/eslint + version: '@renovatebot/eslint-plugin@file:tools/eslint' '@semantic-release/exec': specifier: 6.0.3 - version: 6.0.3(semantic-release@22.0.12) + version: 6.0.3(semantic-release@22.0.12(typescript@5.4.5)) '@swc/core': - specifier: 1.4.2 - version: 1.4.2 + specifier: 1.5.7 + version: 1.5.7 '@types/auth-header': specifier: 1.0.6 version: 1.0.6 '@types/aws4': specifier: 1.11.6 version: 1.11.6 + '@types/better-sqlite3': + specifier: 7.6.10 + version: 7.6.10 '@types/breejs__later': specifier: 4.1.5 version: 4.1.5 @@ -401,11 +404,11 @@ importers: specifier: 1.0.2 version: 1.0.2 '@types/diff': - specifier: 5.0.9 - version: 5.0.9 + specifier: 5.2.1 + version: 5.2.1 '@types/eslint': - specifier: 8.56.3 - version: 8.56.3 + specifier: 8.56.10 + version: 8.56.10 '@types/fs-extra': specifier: 11.0.4 version: 11.0.4 @@ -419,8 +422,8 @@ importers: specifier: 2.1.3 version: 2.1.3 '@types/ini': - specifier: 1.3.34 - version: 1.3.34 + specifier: 4.1.0 + version: 4.1.0 '@types/js-yaml': specifier: 4.0.9 version: 4.0.9 @@ -431,14 +434,14 @@ importers: specifier: 1.0.3 version: 1.0.3 '@types/lodash': - specifier: 4.14.202 - version: 4.14.202 + specifier: 4.17.1 + version: 4.17.1 '@types/luxon': specifier: 3.4.2 version: 3.4.2 '@types/markdown-it': - specifier: 13.0.7 - version: 13.0.7 + specifier: 14.0.1 + version: 14.0.1 '@types/markdown-table': specifier: 2.0.0 version: 2.0.0 @@ -451,21 +454,18 @@ importers: '@types/moo': specifier: 0.5.9 version: 0.5.9 - '@types/nock': - specifier: 10.0.3 - version: 10.0.3 + '@types/ms': + specifier: 0.7.34 + version: 0.7.34 '@types/node': - specifier: 18.19.21 - version: 18.19.21 + specifier: 18.19.33 + version: 18.19.33 '@types/parse-link-header': specifier: 2.0.3 version: 2.0.3 - '@types/prettier': - specifier: 2.7.3 - version: 2.7.3 '@types/semver': - specifier: 7.5.6 - version: 7.5.6 + specifier: 7.5.8 + version: 7.5.8 '@types/semver-stable': specifier: 3.0.2 version: 3.0.2 @@ -473,11 +473,17 @@ importers: specifier: 1.1.3 version: 1.1.3 '@types/tar': - specifier: 6.1.11 - version: 6.1.11 + specifier: 6.1.13 + version: 6.1.13 + '@types/tmp': + specifier: 0.2.6 + version: 0.2.6 '@types/traverse': specifier: 0.6.36 version: 0.6.36 + '@types/unist': + specifier: 2.0.10 + version: 2.0.10 '@types/url-join': specifier: 4.0.3 version: 4.0.3 @@ -488,14 +494,14 @@ importers: specifier: 1.1.9 version: 1.1.9 '@typescript-eslint/eslint-plugin': - specifier: 6.21.0 - version: 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3) + specifier: 7.10.0 + version: 7.10.0(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/parser': - specifier: 6.21.0 - version: 6.21.0(eslint@8.56.0)(typescript@5.3.3) + specifier: 7.10.0 + version: 7.10.0(eslint@8.57.0)(typescript@5.4.5) aws-sdk-client-mock: - specifier: 3.0.1 - version: 3.0.1 + specifier: 4.0.0 + version: 4.0.0 callsite: specifier: 1.0.0 version: 1.0.0 @@ -512,32 +518,29 @@ importers: specifier: 15.3.0 version: 15.3.0(emojibase@15.3.0) eslint: - specifier: 8.56.0 - version: 8.56.0 - eslint-config-prettier: - specifier: 9.1.0 - version: 9.1.0(eslint@8.56.0) + specifier: 8.57.0 + version: 8.57.0 eslint-formatter-gha: - specifier: 1.4.3 - version: 1.4.3 + specifier: 1.5.0 + version: 1.5.0 eslint-import-resolver-typescript: specifier: 3.6.1 - version: 3.6.1(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0) + version: 3.6.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0) eslint-plugin-import: specifier: 2.29.1 - version: 2.29.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) + version: 2.29.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jest: specifier: 27.9.0 - version: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.56.0)(jest@29.7.0)(typescript@5.3.3) + version: 27.9.0(@typescript-eslint/eslint-plugin@7.10.0(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5)))(typescript@5.4.5) eslint-plugin-jest-formatting: specifier: 3.1.0 - version: 3.1.0(eslint@8.56.0) + version: 3.1.0(eslint@8.57.0) eslint-plugin-promise: specifier: 6.1.1 - version: 6.1.1(eslint@8.56.0) + version: 6.1.1(eslint@8.57.0) eslint-plugin-typescript-enum: specifier: 2.1.0 - version: 2.1.0(eslint@8.56.0)(typescript@5.3.3) + version: 2.1.0(eslint@8.57.0)(typescript@5.4.5) expect: specifier: 29.7.0 version: 29.7.0 @@ -552,28 +555,28 @@ importers: version: 9.0.11 jest: specifier: 29.7.0 - version: 29.7.0(@types/node@18.19.21)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5)) jest-extended: specifier: 4.0.2 - version: 4.0.2(jest@29.7.0) + version: 4.0.2(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5))) jest-mock: specifier: 29.7.0 version: 29.7.0 jest-mock-extended: - specifier: 3.0.5 - version: 3.0.5(jest@29.7.0)(typescript@5.3.3) + specifier: 3.0.7 + version: 3.0.7(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5)))(typescript@5.4.5) jest-snapshot: specifier: 29.7.0 version: 29.7.0 markdownlint-cli2: - specifier: 0.12.1 - version: 0.12.1 + specifier: 0.13.0 + version: 0.13.0 memfs: - specifier: 4.7.7 - version: 4.7.7 + specifier: 4.9.2 + version: 4.9.2 nock: - specifier: 13.5.3 - version: 13.5.3 + specifier: 13.5.4 + version: 13.5.4 npm-run-all2: specifier: 6.1.2 version: 6.1.2 @@ -584,1016 +587,6663 @@ importers: specifier: 29.7.0 version: 29.7.0 rimraf: - specifier: 5.0.5 - version: 5.0.5 + specifier: 5.0.7 + version: 5.0.7 semantic-release: specifier: 22.0.12 - version: 22.0.12(typescript@5.3.3) + version: 22.0.12(typescript@5.4.5) tar: - specifier: 6.2.0 - version: 6.2.0 + specifier: 6.2.1 + version: 6.2.1 tmp-promise: specifier: 3.0.3 version: 3.0.3 ts-jest: specifier: 29.1.2 - version: 29.1.2(@babel/core@7.23.9)(@jest/types@29.6.3)(jest@29.7.0)(typescript@5.3.3) + version: 29.1.2(@babel/core@7.24.5)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.5))(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5)))(typescript@5.4.5) ts-node: specifier: 10.9.2 - version: 10.9.2(@swc/core@1.4.2)(@types/node@18.19.21)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5) type-fest: - specifier: 4.10.3 - version: 4.10.3 + specifier: 4.18.2 + version: 4.18.2 typescript: - specifier: 5.3.3 - version: 5.3.3 + specifier: 5.4.5 + version: 5.4.5 unified: specifier: 9.2.2 version: 9.2.2 packages: - /@aashutoshrathi/word-wrap@1.2.6: - resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} - engines: {node: '>=0.10.0'} - dev: true - - /@actions/core@1.10.1: - resolution: {integrity: sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==} - dependencies: - '@actions/http-client': 2.2.0 - uuid: 8.3.2 - dev: true - - /@actions/http-client@2.2.0: - resolution: {integrity: sha512-q+epW0trjVUUHboliPb4UF9g2msf+w61b32tAkFEwL/IwP0DQWgbCMM0Hbe3e3WXSKz5VcUXbzJQgy8Hkra/Lg==} - dependencies: - tunnel: 0.0.6 - undici: 5.28.3 - dev: true - - /@ampproject/remapping@2.2.1: - resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/gen-mapping': 0.3.4 - '@jridgewell/trace-mapping': 0.3.23 - dev: true - /@arcanis/slice-ansi@1.1.1: + '@arcanis/slice-ansi@1.1.1': resolution: {integrity: sha512-xguP2WR2Dv0gQ7Ykbdb7BNCnPnIPB94uTi0Z2NvkRBEnhbwjOQ7QyQKJXrVQg4qDpiD9hA5l5cCwy/z2OXgc3w==} - dependencies: - grapheme-splitter: 1.0.4 - dev: false - /@aws-crypto/crc32@3.0.0: + '@aws-crypto/crc32@3.0.0': resolution: {integrity: sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==} - dependencies: - '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.357.0 - tslib: 1.14.1 - dev: false - /@aws-crypto/crc32c@3.0.0: + '@aws-crypto/crc32c@3.0.0': resolution: {integrity: sha512-ENNPPManmnVJ4BTXlOjAgD7URidbAznURqD0KvfREyc4o20DPYdEldU1f5cQ7Jbj0CJJSPaMIk/9ZshdB3210w==} - dependencies: - '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.357.0 - tslib: 1.14.1 - dev: false - /@aws-crypto/ie11-detection@3.0.0: + '@aws-crypto/ie11-detection@3.0.0': resolution: {integrity: sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==} - dependencies: - tslib: 1.14.1 - dev: false - /@aws-crypto/sha1-browser@3.0.0: + '@aws-crypto/sha1-browser@3.0.0': resolution: {integrity: sha512-NJth5c997GLHs6nOYTzFKTbYdMNA6/1XlKVgnZoaZcQ7z7UJlOgj2JdbHE8tiYLS3fzXNCguct77SPGat2raSw==} - dependencies: - '@aws-crypto/ie11-detection': 3.0.0 - '@aws-crypto/supports-web-crypto': 3.0.0 - '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.357.0 - '@aws-sdk/util-locate-window': 3.495.0 - '@aws-sdk/util-utf8-browser': 3.259.0 - tslib: 1.14.1 - dev: false - /@aws-crypto/sha256-browser@3.0.0: + '@aws-crypto/sha256-browser@3.0.0': resolution: {integrity: sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==} - dependencies: - '@aws-crypto/ie11-detection': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-crypto/supports-web-crypto': 3.0.0 - '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.357.0 - '@aws-sdk/util-locate-window': 3.495.0 - '@aws-sdk/util-utf8-browser': 3.259.0 - tslib: 1.14.1 - dev: false - /@aws-crypto/sha256-js@3.0.0: + '@aws-crypto/sha256-js@3.0.0': resolution: {integrity: sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==} - dependencies: - '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.357.0 - tslib: 1.14.1 - dev: false - /@aws-crypto/supports-web-crypto@3.0.0: + '@aws-crypto/supports-web-crypto@3.0.0': resolution: {integrity: sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==} - dependencies: - tslib: 1.14.1 - dev: false - /@aws-crypto/util@3.0.0: + '@aws-crypto/util@3.0.0': resolution: {integrity: sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==} - dependencies: - '@aws-sdk/types': 3.357.0 - '@aws-sdk/util-utf8-browser': 3.259.0 - tslib: 1.14.1 - dev: false - /@aws-sdk/chunked-blob-reader@3.310.0: - resolution: {integrity: sha512-CrJS3exo4mWaLnWxfCH+w88Ou0IcAZSIkk4QbmxiHl/5Dq705OLoxf4385MVyExpqpeVJYOYQ2WaD8i/pQZ2fg==} - dependencies: - tslib: 2.6.2 - dev: false - - /@aws-sdk/client-codecommit@3.363.0: - resolution: {integrity: sha512-0LXm3j96YolXa9KlIONOYiR9uistdYlP5Vr+9/wUGocfkLDSfsOTQF4sLPH5seytzY8F5U+4mGY/hweCCHK+Qw==} + '@aws-sdk/client-codecommit@3.565.0': + resolution: {integrity: sha512-TkhNsQNafdeOqtb15Ck2BGjtiXTK/K0NFsVMO4nTgSBCXXq2TA4CuaTOEaek2FNGxU5AHVTUDmmSMee1vPMLvg==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sts': 3.363.0 - '@aws-sdk/credential-provider-node': 3.363.0 - '@aws-sdk/middleware-host-header': 3.363.0 - '@aws-sdk/middleware-logger': 3.363.0 - '@aws-sdk/middleware-recursion-detection': 3.363.0 - '@aws-sdk/middleware-signing': 3.363.0 - '@aws-sdk/middleware-user-agent': 3.363.0 - '@aws-sdk/types': 3.357.0 - '@aws-sdk/util-endpoints': 3.357.0 - '@aws-sdk/util-user-agent-browser': 3.363.0 - '@aws-sdk/util-user-agent-node': 3.363.0 - '@smithy/config-resolver': 1.1.0 - '@smithy/fetch-http-handler': 1.1.0 - '@smithy/hash-node': 1.1.0 - '@smithy/invalid-dependency': 1.1.0 - '@smithy/middleware-content-length': 1.1.0 - '@smithy/middleware-endpoint': 1.1.0 - '@smithy/middleware-retry': 1.1.0 - '@smithy/middleware-serde': 1.1.0 - '@smithy/middleware-stack': 1.1.0 - '@smithy/node-config-provider': 1.1.0 - '@smithy/node-http-handler': 1.1.0 - '@smithy/protocol-http': 1.2.0 - '@smithy/smithy-client': 1.1.0 - '@smithy/types': 1.2.0 - '@smithy/url-parser': 1.1.0 - '@smithy/util-base64': 1.1.0 - '@smithy/util-body-length-browser': 1.1.0 - '@smithy/util-body-length-node': 1.1.0 - '@smithy/util-defaults-mode-browser': 1.1.0 - '@smithy/util-defaults-mode-node': 1.1.0 - '@smithy/util-retry': 1.1.0 - '@smithy/util-utf8': 1.1.0 - tslib: 2.6.2 - uuid: 8.3.2 - transitivePeerDependencies: - - aws-crt - dev: false - /@aws-sdk/client-cognito-identity@3.363.0: - resolution: {integrity: sha512-tsJzgBSCpna85IVsuS7FBIK9wkSl7fs8TJ/QzapIgu8rKss0ySHVO6TeMVAdw2BvaQl7CxU9c3PosjhLWHu6KQ==} + '@aws-sdk/client-cognito-identity@3.565.0': + resolution: {integrity: sha512-g3CycpQTqw4YW9BbX/0Z7cXO3v5x4s0SUDmYu5qEE3ziUmeTxqQc0aJN5wPjuKbF0UuQ4oEnTiMV/yZQrcMEIQ==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sts': 3.363.0 - '@aws-sdk/credential-provider-node': 3.363.0 - '@aws-sdk/middleware-host-header': 3.363.0 - '@aws-sdk/middleware-logger': 3.363.0 - '@aws-sdk/middleware-recursion-detection': 3.363.0 - '@aws-sdk/middleware-signing': 3.363.0 - '@aws-sdk/middleware-user-agent': 3.363.0 - '@aws-sdk/types': 3.357.0 - '@aws-sdk/util-endpoints': 3.357.0 - '@aws-sdk/util-user-agent-browser': 3.363.0 - '@aws-sdk/util-user-agent-node': 3.363.0 - '@smithy/config-resolver': 1.1.0 - '@smithy/fetch-http-handler': 1.1.0 - '@smithy/hash-node': 1.1.0 - '@smithy/invalid-dependency': 1.1.0 - '@smithy/middleware-content-length': 1.1.0 - '@smithy/middleware-endpoint': 1.1.0 - '@smithy/middleware-retry': 1.1.0 - '@smithy/middleware-serde': 1.1.0 - '@smithy/middleware-stack': 1.1.0 - '@smithy/node-config-provider': 1.1.0 - '@smithy/node-http-handler': 1.1.0 - '@smithy/protocol-http': 1.2.0 - '@smithy/smithy-client': 1.1.0 - '@smithy/types': 1.2.0 - '@smithy/url-parser': 1.1.0 - '@smithy/util-base64': 1.1.0 - '@smithy/util-body-length-browser': 1.1.0 - '@smithy/util-body-length-node': 1.1.0 - '@smithy/util-defaults-mode-browser': 1.1.0 - '@smithy/util-defaults-mode-node': 1.1.0 - '@smithy/util-retry': 1.1.0 - '@smithy/util-utf8': 1.1.0 - tslib: 2.6.2 - transitivePeerDependencies: - - aws-crt - dev: false - /@aws-sdk/client-ec2@3.363.0: - resolution: {integrity: sha512-AnF8o9PxxqLOtMh6kpxvlLkdabXsR7aIwViiTFh8lmsE0wRm5RGnlIOtEvGD4ze2o96PmB/vKrsise1W3d8YXA==} + '@aws-sdk/client-ec2@3.565.0': + resolution: {integrity: sha512-NL7lfRDWrFiLXQLDMSpwUqWoS2wSpCjBbZFylbKjfnzoUi1N7L2I6xu9fIFY6rN/iQTU8SEl3Pxc+32G8MLe8A==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sts': 3.363.0 - '@aws-sdk/credential-provider-node': 3.363.0 - '@aws-sdk/middleware-host-header': 3.363.0 - '@aws-sdk/middleware-logger': 3.363.0 - '@aws-sdk/middleware-recursion-detection': 3.363.0 - '@aws-sdk/middleware-sdk-ec2': 3.363.0 - '@aws-sdk/middleware-signing': 3.363.0 - '@aws-sdk/middleware-user-agent': 3.363.0 - '@aws-sdk/types': 3.357.0 - '@aws-sdk/util-endpoints': 3.357.0 - '@aws-sdk/util-user-agent-browser': 3.363.0 - '@aws-sdk/util-user-agent-node': 3.363.0 - '@smithy/config-resolver': 1.1.0 - '@smithy/fetch-http-handler': 1.1.0 - '@smithy/hash-node': 1.1.0 - '@smithy/invalid-dependency': 1.1.0 - '@smithy/middleware-content-length': 1.1.0 - '@smithy/middleware-endpoint': 1.1.0 - '@smithy/middleware-retry': 1.1.0 - '@smithy/middleware-serde': 1.1.0 - '@smithy/middleware-stack': 1.1.0 - '@smithy/node-config-provider': 1.1.0 - '@smithy/node-http-handler': 1.1.0 - '@smithy/protocol-http': 1.2.0 - '@smithy/smithy-client': 1.1.0 - '@smithy/types': 1.2.0 - '@smithy/url-parser': 1.1.0 - '@smithy/util-base64': 1.1.0 - '@smithy/util-body-length-browser': 1.1.0 - '@smithy/util-body-length-node': 1.1.0 - '@smithy/util-defaults-mode-browser': 1.1.0 - '@smithy/util-defaults-mode-node': 1.1.0 - '@smithy/util-retry': 1.1.0 - '@smithy/util-utf8': 1.1.0 - '@smithy/util-waiter': 1.1.0 - fast-xml-parser: 4.2.5 - tslib: 2.6.2 - uuid: 8.3.2 - transitivePeerDependencies: - - aws-crt - dev: false - /@aws-sdk/client-ecr@3.363.0: - resolution: {integrity: sha512-KKcvsO8puIYpvlkMTJrAMA5Alnm1DBFuuAGuYxhTiSs009N2XPN0ETftBbZOlWpyVvZ9p1ukZkSqzngc41NyJw==} + '@aws-sdk/client-ecr@3.565.0': + resolution: {integrity: sha512-d6LRF1z4nCVUOyVaDGnm8hRoCX7LO5QDO1M4ExXvwqfWfdq9Ld2aAPCArpyAVmyPZNlIo0VTblJd/Kuvrc00Gg==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sts': 3.363.0 - '@aws-sdk/credential-provider-node': 3.363.0 - '@aws-sdk/middleware-host-header': 3.363.0 - '@aws-sdk/middleware-logger': 3.363.0 - '@aws-sdk/middleware-recursion-detection': 3.363.0 - '@aws-sdk/middleware-signing': 3.363.0 - '@aws-sdk/middleware-user-agent': 3.363.0 - '@aws-sdk/types': 3.357.0 - '@aws-sdk/util-endpoints': 3.357.0 - '@aws-sdk/util-user-agent-browser': 3.363.0 - '@aws-sdk/util-user-agent-node': 3.363.0 - '@smithy/config-resolver': 1.1.0 - '@smithy/fetch-http-handler': 1.1.0 - '@smithy/hash-node': 1.1.0 - '@smithy/invalid-dependency': 1.1.0 - '@smithy/middleware-content-length': 1.1.0 - '@smithy/middleware-endpoint': 1.1.0 - '@smithy/middleware-retry': 1.1.0 - '@smithy/middleware-serde': 1.1.0 - '@smithy/middleware-stack': 1.1.0 - '@smithy/node-config-provider': 1.1.0 - '@smithy/node-http-handler': 1.1.0 - '@smithy/protocol-http': 1.2.0 - '@smithy/smithy-client': 1.1.0 - '@smithy/types': 1.2.0 - '@smithy/url-parser': 1.1.0 - '@smithy/util-base64': 1.1.0 - '@smithy/util-body-length-browser': 1.1.0 - '@smithy/util-body-length-node': 1.1.0 - '@smithy/util-defaults-mode-browser': 1.1.0 - '@smithy/util-defaults-mode-node': 1.1.0 - '@smithy/util-retry': 1.1.0 - '@smithy/util-utf8': 1.1.0 - '@smithy/util-waiter': 1.1.0 - tslib: 2.6.2 - transitivePeerDependencies: - - aws-crt - dev: false - /@aws-sdk/client-rds@3.363.0: - resolution: {integrity: sha512-ssd8MC+rJztnq0jBFQxG+zioFqglUVTbkRI5nS4qEXSIG9XcRy7+zQklwEEMg8maad2vcTY0s8/HogsgTFk9WA==} + '@aws-sdk/client-rds@3.565.0': + resolution: {integrity: sha512-aNCEY6861jh4qYQg4Hl1OTG5fMOkRU0q4OibLl26V+WKU9gWFlR+JHF2m247rMuWd23ygpqpQ5OmcdqTJuRFmg==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sts': 3.363.0 - '@aws-sdk/credential-provider-node': 3.363.0 - '@aws-sdk/middleware-host-header': 3.363.0 - '@aws-sdk/middleware-logger': 3.363.0 - '@aws-sdk/middleware-recursion-detection': 3.363.0 - '@aws-sdk/middleware-sdk-rds': 3.363.0 - '@aws-sdk/middleware-signing': 3.363.0 - '@aws-sdk/middleware-user-agent': 3.363.0 - '@aws-sdk/types': 3.357.0 - '@aws-sdk/util-endpoints': 3.357.0 - '@aws-sdk/util-user-agent-browser': 3.363.0 - '@aws-sdk/util-user-agent-node': 3.363.0 - '@smithy/config-resolver': 1.1.0 - '@smithy/fetch-http-handler': 1.1.0 - '@smithy/hash-node': 1.1.0 - '@smithy/invalid-dependency': 1.1.0 - '@smithy/middleware-content-length': 1.1.0 - '@smithy/middleware-endpoint': 1.1.0 - '@smithy/middleware-retry': 1.1.0 - '@smithy/middleware-serde': 1.1.0 - '@smithy/middleware-stack': 1.1.0 - '@smithy/node-config-provider': 1.1.0 - '@smithy/node-http-handler': 1.1.0 - '@smithy/protocol-http': 1.2.0 - '@smithy/smithy-client': 1.1.0 - '@smithy/types': 1.2.0 - '@smithy/url-parser': 1.1.0 - '@smithy/util-base64': 1.1.0 - '@smithy/util-body-length-browser': 1.1.0 - '@smithy/util-body-length-node': 1.1.0 - '@smithy/util-defaults-mode-browser': 1.1.0 - '@smithy/util-defaults-mode-node': 1.1.0 - '@smithy/util-retry': 1.1.0 - '@smithy/util-utf8': 1.1.0 - '@smithy/util-waiter': 1.1.0 - fast-xml-parser: 4.2.5 - tslib: 2.6.2 - transitivePeerDependencies: - - aws-crt - dev: false - /@aws-sdk/client-s3@3.363.0: - resolution: {integrity: sha512-LNnfg/t8wG5Fqj6l+PSV/t+IXDq9r3Kj9jEHn84513+p7bewXYSSreSpmLjG8OcKuMfHc9EJGNQ3DkMyFaLoWg==} + '@aws-sdk/client-s3@3.565.0': + resolution: {integrity: sha512-e5ioE9XBV6bJTrCClvCpK9vGP+Dp69y/LcC4ENfPcEM+BniQau2StCWcNkFuvVXyuKuk0drS+ZLnP+tefNEJ4A==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-crypto/sha1-browser': 3.0.0 - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sts': 3.363.0 - '@aws-sdk/credential-provider-node': 3.363.0 - '@aws-sdk/hash-blob-browser': 3.357.0 - '@aws-sdk/hash-stream-node': 3.357.0 - '@aws-sdk/md5-js': 3.357.0 - '@aws-sdk/middleware-bucket-endpoint': 3.363.0 - '@aws-sdk/middleware-expect-continue': 3.363.0 - '@aws-sdk/middleware-flexible-checksums': 3.363.0 - '@aws-sdk/middleware-host-header': 3.363.0 - '@aws-sdk/middleware-location-constraint': 3.363.0 - '@aws-sdk/middleware-logger': 3.363.0 - '@aws-sdk/middleware-recursion-detection': 3.363.0 - '@aws-sdk/middleware-sdk-s3': 3.363.0 - '@aws-sdk/middleware-signing': 3.363.0 - '@aws-sdk/middleware-ssec': 3.363.0 - '@aws-sdk/middleware-user-agent': 3.363.0 - '@aws-sdk/signature-v4-multi-region': 3.363.0 - '@aws-sdk/types': 3.357.0 - '@aws-sdk/util-endpoints': 3.357.0 - '@aws-sdk/util-user-agent-browser': 3.363.0 - '@aws-sdk/util-user-agent-node': 3.363.0 - '@aws-sdk/xml-builder': 3.310.0 - '@smithy/config-resolver': 1.1.0 - '@smithy/eventstream-serde-browser': 1.1.0 - '@smithy/eventstream-serde-config-resolver': 1.1.0 - '@smithy/eventstream-serde-node': 1.1.0 - '@smithy/fetch-http-handler': 1.1.0 - '@smithy/hash-node': 1.1.0 - '@smithy/invalid-dependency': 1.1.0 - '@smithy/middleware-content-length': 1.1.0 - '@smithy/middleware-endpoint': 1.1.0 - '@smithy/middleware-retry': 1.1.0 - '@smithy/middleware-serde': 1.1.0 - '@smithy/middleware-stack': 1.1.0 - '@smithy/node-config-provider': 1.1.0 - '@smithy/node-http-handler': 1.1.0 - '@smithy/protocol-http': 1.2.0 - '@smithy/smithy-client': 1.1.0 - '@smithy/types': 1.2.0 - '@smithy/url-parser': 1.1.0 - '@smithy/util-base64': 1.1.0 - '@smithy/util-body-length-browser': 1.1.0 - '@smithy/util-body-length-node': 1.1.0 - '@smithy/util-defaults-mode-browser': 1.1.0 - '@smithy/util-defaults-mode-node': 1.1.0 - '@smithy/util-retry': 1.1.0 - '@smithy/util-stream': 1.1.0 - '@smithy/util-utf8': 1.1.0 - '@smithy/util-waiter': 1.1.0 - fast-xml-parser: 4.2.5 - tslib: 2.6.2 - transitivePeerDependencies: - - '@aws-sdk/signature-v4-crt' - - aws-crt - dev: false - /@aws-sdk/client-sso-oidc@3.363.0: - resolution: {integrity: sha512-V3Ebiq/zNtDS/O92HUWGBa7MY59RYSsqWd+E0XrXv6VYTA00RlMTbNcseivNgp2UghOgB9a20Nkz6EqAeIN+RQ==} - engines: {node: '>=14.0.0'} - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/middleware-host-header': 3.363.0 - '@aws-sdk/middleware-logger': 3.363.0 - '@aws-sdk/middleware-recursion-detection': 3.363.0 - '@aws-sdk/middleware-user-agent': 3.363.0 - '@aws-sdk/types': 3.357.0 - '@aws-sdk/util-endpoints': 3.357.0 - '@aws-sdk/util-user-agent-browser': 3.363.0 - '@aws-sdk/util-user-agent-node': 3.363.0 - '@smithy/config-resolver': 1.1.0 - '@smithy/fetch-http-handler': 1.1.0 - '@smithy/hash-node': 1.1.0 - '@smithy/invalid-dependency': 1.1.0 - '@smithy/middleware-content-length': 1.1.0 - '@smithy/middleware-endpoint': 1.1.0 - '@smithy/middleware-retry': 1.1.0 - '@smithy/middleware-serde': 1.1.0 - '@smithy/middleware-stack': 1.1.0 - '@smithy/node-config-provider': 1.1.0 - '@smithy/node-http-handler': 1.1.0 - '@smithy/protocol-http': 1.2.0 - '@smithy/smithy-client': 1.1.0 - '@smithy/types': 1.2.0 - '@smithy/url-parser': 1.1.0 - '@smithy/util-base64': 1.1.0 - '@smithy/util-body-length-browser': 1.1.0 - '@smithy/util-body-length-node': 1.1.0 - '@smithy/util-defaults-mode-browser': 1.1.0 - '@smithy/util-defaults-mode-node': 1.1.0 - '@smithy/util-retry': 1.1.0 - '@smithy/util-utf8': 1.1.0 - tslib: 2.6.2 - transitivePeerDependencies: - - aws-crt - dev: false + '@aws-sdk/client-sso-oidc@3.569.0': + resolution: {integrity: sha512-u5DEjNEvRvlKKh1QLCDuQ8GIrx+OFvJFLfhorsp4oCxDylvORs+KfyKKnJAw4wYEEHyxyz9GzHD7p6a8+HLVHw==} + engines: {node: '>=16.0.0'} - /@aws-sdk/client-sso@3.363.0: - resolution: {integrity: sha512-PZ+HfKSgS4hlMnJzG+Ev8/mgHd/b/ETlJWPSWjC/f2NwVoBQkBnqHjdyEx7QjF6nksJozcVh5Q+kkYLKc/QwBQ==} + '@aws-sdk/client-sso@3.556.0': + resolution: {integrity: sha512-unXdWS7uvHqCcOyC1de+Fr8m3F2vMg2m24GPea0bg7rVGTYmiyn9mhUX11VCt+ozydrw+F50FQwL6OqoqPocmw==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/middleware-host-header': 3.363.0 - '@aws-sdk/middleware-logger': 3.363.0 - '@aws-sdk/middleware-recursion-detection': 3.363.0 - '@aws-sdk/middleware-user-agent': 3.363.0 - '@aws-sdk/types': 3.357.0 - '@aws-sdk/util-endpoints': 3.357.0 - '@aws-sdk/util-user-agent-browser': 3.363.0 - '@aws-sdk/util-user-agent-node': 3.363.0 - '@smithy/config-resolver': 1.1.0 - '@smithy/fetch-http-handler': 1.1.0 - '@smithy/hash-node': 1.1.0 - '@smithy/invalid-dependency': 1.1.0 - '@smithy/middleware-content-length': 1.1.0 - '@smithy/middleware-endpoint': 1.1.0 - '@smithy/middleware-retry': 1.1.0 - '@smithy/middleware-serde': 1.1.0 - '@smithy/middleware-stack': 1.1.0 - '@smithy/node-config-provider': 1.1.0 - '@smithy/node-http-handler': 1.1.0 - '@smithy/protocol-http': 1.2.0 - '@smithy/smithy-client': 1.1.0 - '@smithy/types': 1.2.0 - '@smithy/url-parser': 1.1.0 - '@smithy/util-base64': 1.1.0 - '@smithy/util-body-length-browser': 1.1.0 - '@smithy/util-body-length-node': 1.1.0 - '@smithy/util-defaults-mode-browser': 1.1.0 - '@smithy/util-defaults-mode-node': 1.1.0 - '@smithy/util-retry': 1.1.0 - '@smithy/util-utf8': 1.1.0 - tslib: 2.6.2 - transitivePeerDependencies: - - aws-crt - dev: false - /@aws-sdk/client-sts@3.363.0: - resolution: {integrity: sha512-0jj14WvBPJQ8xr72cL0mhlmQ90tF0O0wqXwSbtog6PsC8+KDE6Yf+WsxsumyI8E5O8u3eYijBL+KdqG07F/y/w==} + '@aws-sdk/client-sso@3.568.0': + resolution: {integrity: sha512-LSD7k0ZBQNWouTN5dYpUkeestoQ+r5u6cp6o+FATKeiFQET85RNA3xJ4WPnOI5rBC1PETKhQXvF44863P3hCaQ==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/client-sts@3.565.0': + resolution: {integrity: sha512-c2T20tz+Akn9uBgmZPPK3VLpgzYGVuHxKNisLwGtGL5NdQSoZZ6HNT08PY3KB12Ou8VcZLv8cvUz2Nivqhg4RA==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/credential-provider-node': 3.363.0 - '@aws-sdk/middleware-host-header': 3.363.0 - '@aws-sdk/middleware-logger': 3.363.0 - '@aws-sdk/middleware-recursion-detection': 3.363.0 - '@aws-sdk/middleware-sdk-sts': 3.363.0 - '@aws-sdk/middleware-signing': 3.363.0 - '@aws-sdk/middleware-user-agent': 3.363.0 - '@aws-sdk/types': 3.357.0 - '@aws-sdk/util-endpoints': 3.357.0 - '@aws-sdk/util-user-agent-browser': 3.363.0 - '@aws-sdk/util-user-agent-node': 3.363.0 - '@smithy/config-resolver': 1.1.0 - '@smithy/fetch-http-handler': 1.1.0 - '@smithy/hash-node': 1.1.0 - '@smithy/invalid-dependency': 1.1.0 - '@smithy/middleware-content-length': 1.1.0 - '@smithy/middleware-endpoint': 1.1.0 - '@smithy/middleware-retry': 1.1.0 - '@smithy/middleware-serde': 1.1.0 - '@smithy/middleware-stack': 1.1.0 - '@smithy/node-config-provider': 1.1.0 - '@smithy/node-http-handler': 1.1.0 - '@smithy/protocol-http': 1.2.0 - '@smithy/smithy-client': 1.1.0 - '@smithy/types': 1.2.0 - '@smithy/url-parser': 1.1.0 - '@smithy/util-base64': 1.1.0 - '@smithy/util-body-length-browser': 1.1.0 - '@smithy/util-body-length-node': 1.1.0 - '@smithy/util-defaults-mode-browser': 1.1.0 - '@smithy/util-defaults-mode-node': 1.1.0 - '@smithy/util-retry': 1.1.0 - '@smithy/util-utf8': 1.1.0 - fast-xml-parser: 4.2.5 - tslib: 2.6.2 - transitivePeerDependencies: - - aws-crt - dev: false - /@aws-sdk/credential-provider-cognito-identity@3.363.0: - resolution: {integrity: sha512-5x42JvqEsBUrm6/qdf0WWe4mlmJjPItxamQhRjuOzeQD/BxsA2W5VS/7n0Ws0e27DNhlnUErcIJd+bBy6j1fqA==} + '@aws-sdk/client-sts@3.569.0': + resolution: {integrity: sha512-3AyipQ2zHszkcTr8n1Sp7CiMUi28aMf1vOhEo0KKi0DWGo1Z1qJEpWeRP363KG0n9/8U3p1IkXGz5FRbpXZxIw==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/core@3.556.0': + resolution: {integrity: sha512-vJaSaHw2kPQlo11j/Rzuz0gk1tEaKdz+2ser0f0qZ5vwFlANjt08m/frU17ctnVKC1s58bxpctO/1P894fHLrA==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-sdk/client-cognito-identity': 3.363.0 - '@aws-sdk/types': 3.357.0 - '@smithy/property-provider': 1.2.0 - '@smithy/types': 1.2.0 - tslib: 2.6.2 - transitivePeerDependencies: - - aws-crt - dev: false - /@aws-sdk/credential-provider-env@3.363.0: - resolution: {integrity: sha512-VAQ3zITT2Q0acht0HezouYnMFKZ2vIOa20X4zQA3WI0HfaP4D6ga6KaenbDcb/4VFiqfqiRHfdyXHP0ThcDRMA==} + '@aws-sdk/core@3.567.0': + resolution: {integrity: sha512-zUDEQhC7blOx6sxhHdT75x98+SXQVdUIMu8z8AjqMWiYK2v4WkOS8i6dOS4E5OjL5J1Ac+ruy8op/Bk4AFqSIw==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/credential-provider-cognito-identity@3.565.0': + resolution: {integrity: sha512-nOI0RYE0aHByaDI8w5Eu855fGOwGuAPEeCUgu8AIhExvDUZ5bmiwMN4TxHJW/+CEgQB8uZcPYCDEUMzqr0yh5w==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-sdk/types': 3.357.0 - '@smithy/property-provider': 1.2.0 - '@smithy/types': 1.2.0 - tslib: 2.6.2 - dev: false - /@aws-sdk/credential-provider-ini@3.363.0: - resolution: {integrity: sha512-ZYN+INoqyX5FVC3rqUxB6O8nOWkr0gHRRBm1suoOlmuFJ/WSlW/uUGthRBY5x1AQQnBF8cpdlxZzGHd41lFVNw==} + '@aws-sdk/credential-provider-env@3.535.0': + resolution: {integrity: sha512-XppwO8c0GCGSAvdzyJOhbtktSEaShg14VJKg8mpMa1XcgqzmcqqHQjtDWbx5rZheY1VdpXZhpEzJkB6LpQejpA==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-sdk/credential-provider-env': 3.363.0 - '@aws-sdk/credential-provider-process': 3.363.0 - '@aws-sdk/credential-provider-sso': 3.363.0 - '@aws-sdk/credential-provider-web-identity': 3.363.0 - '@aws-sdk/types': 3.357.0 - '@smithy/credential-provider-imds': 1.1.0 - '@smithy/property-provider': 1.2.0 - '@smithy/shared-ini-file-loader': 1.1.0 - '@smithy/types': 1.2.0 - tslib: 2.6.2 - transitivePeerDependencies: - - aws-crt - dev: false - /@aws-sdk/credential-provider-node@3.363.0: - resolution: {integrity: sha512-C1qXFIN2yMxD6pGgug0vR1UhScOki6VqdzuBHzXZAGu7MOjvgHNdscEcb3CpWnITHaPL2ztkiw75T1sZ7oIgQg==} + '@aws-sdk/credential-provider-env@3.568.0': + resolution: {integrity: sha512-MVTQoZwPnP1Ev5A7LG+KzeU6sCB8BcGkZeDT1z1V5Wt7GPq0MgFQTSSjhImnB9jqRSZkl1079Bt3PbO6lfIS8g==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/credential-provider-http@3.552.0': + resolution: {integrity: sha512-vsmu7Cz1i45pFEqzVb4JcFmAmVnWFNLsGheZc8SCptlqCO5voETrZZILHYIl4cjKkSDk3pblBOf0PhyjqWW6WQ==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-sdk/credential-provider-env': 3.363.0 - '@aws-sdk/credential-provider-ini': 3.363.0 - '@aws-sdk/credential-provider-process': 3.363.0 - '@aws-sdk/credential-provider-sso': 3.363.0 - '@aws-sdk/credential-provider-web-identity': 3.363.0 - '@aws-sdk/types': 3.357.0 - '@smithy/credential-provider-imds': 1.1.0 - '@smithy/property-provider': 1.2.0 - '@smithy/shared-ini-file-loader': 1.1.0 - '@smithy/types': 1.2.0 - tslib: 2.6.2 - transitivePeerDependencies: - - aws-crt - dev: false - /@aws-sdk/credential-provider-process@3.363.0: - resolution: {integrity: sha512-fOKAINU7Rtj2T8pP13GdCt+u0Ml3gYynp8ki+1jMZIQ+Ju/MdDOqZpKMFKicMn3Z1ttUOgqr+grUdus6z8ceBQ==} + '@aws-sdk/credential-provider-http@3.568.0': + resolution: {integrity: sha512-gL0NlyI2eW17hnCrh45hZV+qjtBquB+Bckiip9R6DIVRKqYcoILyiFhuOgf2bXeF23gVh6j18pvUvIoTaFWs5w==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/credential-provider-ini@3.565.0': + resolution: {integrity: sha512-H9+etKKjeQot3vKzuE/osTb1xMzYW0UNQZSLSt1T4fZYSMdEgnOFXRwT0kw8yGMtSQuWMYZcXYHv0jMYetho4A==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-sdk/types': 3.357.0 - '@smithy/property-provider': 1.2.0 - '@smithy/shared-ini-file-loader': 1.1.0 - '@smithy/types': 1.2.0 - tslib: 2.6.2 - dev: false + peerDependencies: + '@aws-sdk/client-sts': ^3.565.0 + + '@aws-sdk/credential-provider-ini@3.568.0': + resolution: {integrity: sha512-m5DUN9mpto5DhEvo6w3+8SS6q932ja37rTNvpPqWJIaWhj7OorAwVirSaJQAQB/M8+XCUIrUonxytphZB28qGQ==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sts': ^3.568.0 - /@aws-sdk/credential-provider-sso@3.363.0: - resolution: {integrity: sha512-5RUZ5oM0lwZSo3EehT0dXggOjgtxFogpT3cZvoLGtIwrPBvm8jOQPXQUlaqCj10ThF1sYltEyukz/ovtDwYGew==} + '@aws-sdk/credential-provider-node@3.565.0': + resolution: {integrity: sha512-d9xlnyd6Ba7DMJNTy0hoAHexFTOx8LWn1XPWbHZqgyRb+0YDIOhPN2ADYxE4Zq+Dc03MLTqq15zWOUhIqAPLuQ==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-sdk/client-sso': 3.363.0 - '@aws-sdk/token-providers': 3.363.0 - '@aws-sdk/types': 3.357.0 - '@smithy/property-provider': 1.2.0 - '@smithy/shared-ini-file-loader': 1.1.0 - '@smithy/types': 1.2.0 - tslib: 2.6.2 - transitivePeerDependencies: - - aws-crt - dev: false - /@aws-sdk/credential-provider-web-identity@3.363.0: - resolution: {integrity: sha512-Z6w7fjgy79pAax580wdixbStQw10xfyZ+hOYLcPudoYFKjoNx0NQBejg5SwBzCF/HQL23Ksm9kDfbXDX9fkPhA==} + '@aws-sdk/credential-provider-node@3.569.0': + resolution: {integrity: sha512-7jH4X2qlPU3PszZP1zvHJorhLARbU1tXvp8ngBe8ArXBrkFpl/dQ2Y/IRAICPm/pyC1IEt8L/CvKp+dz7v/eRw==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/credential-provider-process@3.535.0': + resolution: {integrity: sha512-9O1OaprGCnlb/kYl8RwmH7Mlg8JREZctB8r9sa1KhSsWFq/SWO0AuJTyowxD7zL5PkeS4eTvzFFHWCa3OO5epA==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-sdk/types': 3.357.0 - '@smithy/property-provider': 1.2.0 - '@smithy/types': 1.2.0 - tslib: 2.6.2 - dev: false - /@aws-sdk/credential-providers@3.363.0: - resolution: {integrity: sha512-hVa1DdYasnLud2EKjDAlDHiV/+H/Zq52chHU00c/R8XwPu1s0kZX3NMmlt0D2HhYqC1mUwtdmE58Jra2POviQQ==} + '@aws-sdk/credential-provider-process@3.568.0': + resolution: {integrity: sha512-r01zbXbanP17D+bQUb7mD8Iu2SuayrrYZ0Slgvx32qgz47msocV9EPCSwI4Hkw2ZtEPCeLQR4XCqFJB1D9P50w==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/credential-provider-sso@3.565.0': + resolution: {integrity: sha512-MWefgFWt5BvVMlbjS0mxolxJPA8BKSnzfbdgGCoyEImuHa3GzVArYDQru4oWk6lD+naZFVHzPjHzEDYMag2KGw==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-sdk/client-cognito-identity': 3.363.0 - '@aws-sdk/client-sso': 3.363.0 - '@aws-sdk/client-sts': 3.363.0 - '@aws-sdk/credential-provider-cognito-identity': 3.363.0 - '@aws-sdk/credential-provider-env': 3.363.0 - '@aws-sdk/credential-provider-ini': 3.363.0 - '@aws-sdk/credential-provider-node': 3.363.0 - '@aws-sdk/credential-provider-process': 3.363.0 - '@aws-sdk/credential-provider-sso': 3.363.0 - '@aws-sdk/credential-provider-web-identity': 3.363.0 - '@aws-sdk/types': 3.357.0 - '@smithy/credential-provider-imds': 1.1.0 - '@smithy/property-provider': 1.2.0 - '@smithy/types': 1.2.0 - tslib: 2.6.2 - transitivePeerDependencies: - - aws-crt - dev: false - /@aws-sdk/hash-blob-browser@3.357.0: - resolution: {integrity: sha512-RDd6UgrGHDmleTnXM9LRSSVa69euSAG2mlNhZMEDWk3OFseXVYqBDaqroVbQ01rM2UAe8MeBFchlV9OmxuVgvw==} - dependencies: - '@aws-sdk/chunked-blob-reader': 3.310.0 - '@aws-sdk/types': 3.357.0 - tslib: 2.6.2 - dev: false + '@aws-sdk/credential-provider-sso@3.568.0': + resolution: {integrity: sha512-+TA77NWOEXMUcfLoOuim6xiyXFg1GqHj55ggI1goTKGVvdHYZ+rhxZbwjI29+ewzPt/qcItDJcvhrjOrg9lCag==} + engines: {node: '>=16.0.0'} - /@aws-sdk/hash-stream-node@3.357.0: - resolution: {integrity: sha512-KZjN1VAw1KHNp+xKVOWBGS+MpaYQTjZFD5f+7QQqW4TfbAkFFwIAEYIHq5Q8Gw+jVh0h61OrV/LyW3J2PVzc+w==} + '@aws-sdk/credential-provider-web-identity@3.565.0': + resolution: {integrity: sha512-+MWMp3jxn93Ol2E2gjjXjqoZDNMao03OErGmGoDKMIlu322jNHTvYZo5W0WBy+615mnDKahbX55MmVBge/FwDg==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-sdk/types': 3.357.0 - '@aws-sdk/util-utf8': 3.310.0 - tslib: 2.6.2 - dev: false + peerDependencies: + '@aws-sdk/client-sts': ^3.565.0 - /@aws-sdk/is-array-buffer@3.310.0: - resolution: {integrity: sha512-urnbcCR+h9NWUnmOtet/s4ghvzsidFmspfhYaHAmSRdy9yDjdjBJMFjjsn85A1ODUktztm+cVncXjQ38WCMjMQ==} + '@aws-sdk/credential-provider-web-identity@3.568.0': + resolution: {integrity: sha512-ZJSmTmoIdg6WqAULjYzaJ3XcbgBzVy36lir6Y0UBMRGaxDgos1AARuX6EcYzXOl+ksLvxt/xMQ+3aYh1LWfKSw==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sts': ^3.568.0 + + '@aws-sdk/credential-providers@3.565.0': + resolution: {integrity: sha512-heCRN2Qrje8Nu8TKo+EMM5ToIRECIuCLfHKf2hvkl9iWUs/a7ailNTWUqhE4gqZKGDvFO9dbvqxwKRKi5YXfiA==} engines: {node: '>=14.0.0'} - dependencies: - tslib: 2.6.2 - dev: false - /@aws-sdk/md5-js@3.357.0: - resolution: {integrity: sha512-to42sFAL7KgV/X9X40LLfEaNMHMGQL6/7mPMVCL/W2BZf3zw5OTl3lAaNyjXA+gO5Uo4lFEiQKAQVKNbr8b8Nw==} - dependencies: - '@aws-sdk/types': 3.357.0 - '@aws-sdk/util-utf8': 3.310.0 - tslib: 2.6.2 - dev: false + '@aws-sdk/middleware-bucket-endpoint@3.535.0': + resolution: {integrity: sha512-7sijlfQsc4UO9Fsl11mU26Y5f9E7g6UoNg/iJUBpC5pgvvmdBRO5UEhbB/gnqvOEPsBXyhmfzbstebq23Qdz7A==} + engines: {node: '>=14.0.0'} - /@aws-sdk/middleware-bucket-endpoint@3.363.0: - resolution: {integrity: sha512-kR8+0X50zslpzRW29q4JbpPMadE1z39ZfGwPaBLKpoWvSGt4x+75FaoK71TH7urPPoFyD2Y+XKGA6YRYTUNHSQ==} + '@aws-sdk/middleware-expect-continue@3.535.0': + resolution: {integrity: sha512-hFKyqUBky0NWCVku8iZ9+PACehx0p6vuMw5YnZf8FVgHP0fode0b/NwQY6UY7oor/GftvRsAlRUAWGNFEGUpwA==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-sdk/types': 3.357.0 - '@aws-sdk/util-arn-parser': 3.310.0 - '@smithy/protocol-http': 1.2.0 - '@smithy/types': 1.2.0 - '@smithy/util-config-provider': 1.1.0 - tslib: 2.6.2 - dev: false - /@aws-sdk/middleware-expect-continue@3.363.0: - resolution: {integrity: sha512-I88xneZp6jRwySmIl9uI7eZCcTsqRVnTDfUr1JiXt7zonqNNm80PVYMs6pwaw7t97ec1AQJcsONjuXZyCMnu5g==} + '@aws-sdk/middleware-flexible-checksums@3.535.0': + resolution: {integrity: sha512-rBIzldY9jjRATxICDX7t77aW6ctqmVDgnuAOgbVT5xgHftt4o7PGWKoMvl/45hYqoQgxVFnCBof9bxkqSBebVA==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-sdk/types': 3.357.0 - '@smithy/protocol-http': 1.2.0 - '@smithy/types': 1.2.0 - tslib: 2.6.2 - dev: false - /@aws-sdk/middleware-flexible-checksums@3.363.0: - resolution: {integrity: sha512-FBYmrMRX01uNximNN0WLgpf97GN4xNTLaKsDlkjYRWKJ+J97ICkvLG0FcSu7+SNCpCdJJBeQ5tRVOPVpUu6nmA==} + '@aws-sdk/middleware-host-header@3.535.0': + resolution: {integrity: sha512-0h6TWjBWtDaYwHMQJI9ulafeS4lLaw1vIxRjbpH0svFRt6Eve+Sy8NlVhECfTU2hNz/fLubvrUxsXoThaLBIew==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-crypto/crc32': 3.0.0 - '@aws-crypto/crc32c': 3.0.0 - '@aws-sdk/types': 3.357.0 - '@smithy/is-array-buffer': 1.1.0 - '@smithy/protocol-http': 1.2.0 - '@smithy/types': 1.2.0 - '@smithy/util-utf8': 1.1.0 - tslib: 2.6.2 - dev: false - /@aws-sdk/middleware-host-header@3.363.0: - resolution: {integrity: sha512-FobpclDCf5Y1ueyJDmb9MqguAdPssNMlnqWQpujhYVABq69KHu73fSCWSauFPUrw7YOpV8kG1uagDF0POSxHzA==} + '@aws-sdk/middleware-host-header@3.567.0': + resolution: {integrity: sha512-zQHHj2N3in9duKghH7AuRNrOMLnKhW6lnmb7dznou068DJtDr76w475sHp2TF0XELsOGENbbBsOlN/S5QBFBVQ==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/middleware-location-constraint@3.535.0': + resolution: {integrity: sha512-SxfS9wfidUZZ+WnlKRTCRn3h+XTsymXRXPJj8VV6hNRNeOwzNweoG3YhQbTowuuNfXf89m9v6meYkBBtkdacKw==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-sdk/types': 3.357.0 - '@smithy/protocol-http': 1.2.0 - '@smithy/types': 1.2.0 - tslib: 2.6.2 - dev: false - /@aws-sdk/middleware-location-constraint@3.363.0: - resolution: {integrity: sha512-piNzpNNI/fChSGOZxcq/2msN2qFUSEAbhqs91zbcpv8CEPekVLc4W9laXCG764BEMyfG97ZU8MtzwHeMhELhBA==} + '@aws-sdk/middleware-logger@3.535.0': + resolution: {integrity: sha512-huNHpONOrEDrdRTvSQr1cJiRMNf0S52NDXtaPzdxiubTkP+vni2MohmZANMOai/qT0olmEVX01LhZ0ZAOgmg6A==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-sdk/types': 3.357.0 - '@smithy/types': 1.2.0 - tslib: 2.6.2 - dev: false - /@aws-sdk/middleware-logger@3.363.0: - resolution: {integrity: sha512-SSGgthScYnFGTOw8EzbkvquqweFmvn7uJihkpFekbtBNGC/jGOGO+8ziHjTQ8t/iI/YKubEwv+LMi0f77HKSEg==} + '@aws-sdk/middleware-logger@3.568.0': + resolution: {integrity: sha512-BinH72RG7K3DHHC1/tCulocFv+ZlQ9SrPF9zYT0T1OT95JXuHhB7fH8gEABrc6DAtOdJJh2fgxQjPy5tzPtsrA==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/middleware-recursion-detection@3.535.0': + resolution: {integrity: sha512-am2qgGs+gwqmR4wHLWpzlZ8PWhm4ktj5bYSgDrsOfjhdBlWNxvPoID9/pDAz5RWL48+oH7I6SQzMqxXsFDikrw==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-sdk/types': 3.357.0 - '@smithy/types': 1.2.0 - tslib: 2.6.2 - dev: false - /@aws-sdk/middleware-recursion-detection@3.363.0: - resolution: {integrity: sha512-MWD/57QgI/N7fG8rtzDTUdSqNpYohQfgj9XCFAoVeI/bU4usrkOrew43L4smJG4XrDxlNT8lSJlDtd64tuiUZA==} + '@aws-sdk/middleware-recursion-detection@3.567.0': + resolution: {integrity: sha512-rFk3QhdT4IL6O/UWHmNdjJiURutBCy+ogGqaNHf/RELxgXH3KmYorLwCe0eFb5hq8f6vr3zl4/iH7YtsUOuo1w==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/middleware-sdk-ec2@3.556.0': + resolution: {integrity: sha512-577F9J+H65T/xYde6iy0RnArw3VAHlf9UTfSuHlJvpc9e6o2rd43FISVCNTdA+RCbJpmdWsaZSws0EYGJD+Sag==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-sdk/types': 3.357.0 - '@smithy/protocol-http': 1.2.0 - '@smithy/types': 1.2.0 - tslib: 2.6.2 - dev: false - /@aws-sdk/middleware-sdk-ec2@3.363.0: - resolution: {integrity: sha512-F/Vj36t4k4gBW/faEJRDNC8D55vFVWjLJCcHq0RWy/vJnsmoMXLSHp++lpmLl1G8YsjjSsCObOcfJL/VHeO6Zg==} + '@aws-sdk/middleware-sdk-rds@3.556.0': + resolution: {integrity: sha512-OVsvkBYT43ftHjxFVEBdS9gngY0yTutg2dM7DIlu1jYxYZnaj56r1tQMSiRVYbIuxiPcs19WE97dgpu9jsZIJg==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-sdk/types': 3.357.0 - '@aws-sdk/util-format-url': 3.363.0 - '@smithy/middleware-endpoint': 1.1.0 - '@smithy/protocol-http': 1.2.0 - '@smithy/signature-v4': 1.1.0 - '@smithy/smithy-client': 1.1.0 - '@smithy/types': 1.2.0 - tslib: 2.6.2 - dev: false - /@aws-sdk/middleware-sdk-rds@3.363.0: - resolution: {integrity: sha512-ZhtQQkw9ehVQ59GIaOIUI17cQQ4xlZ3uVlGEuQorKhHx2wydmiqisp3inI2OkbR+4NVqGKrdprRfKc9MhD92HQ==} + '@aws-sdk/middleware-sdk-s3@3.556.0': + resolution: {integrity: sha512-4W/dnxqj1B6/uS/5Z+3UHaqDDGjNPgEVlqf5d3ToOFZ31ZfpANwhcCmyX39JklC4aolCEi9renQ5wHnTCC8K8g==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-sdk/types': 3.357.0 - '@aws-sdk/util-format-url': 3.363.0 - '@smithy/middleware-endpoint': 1.1.0 - '@smithy/protocol-http': 1.2.0 - '@smithy/signature-v4': 1.1.0 - '@smithy/types': 1.2.0 - tslib: 2.6.2 - dev: false - /@aws-sdk/middleware-sdk-s3@3.363.0: - resolution: {integrity: sha512-npC8vLCero+vULizrK0QPjNanWbgH4A/2Llc1nO8N005uvUe7co6WglILF2W3guZrFk/0uGEdX67OnLxUD97pw==} + '@aws-sdk/middleware-signing@3.556.0': + resolution: {integrity: sha512-kWrPmU8qd3gI5qzpuW9LtWFaH80cOz1ZJDavXx6PRpYZJ5JaKdUHghwfDlVTzzFYAeJmVsWIkPcLT5d5mY5ZTQ==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-sdk/types': 3.357.0 - '@aws-sdk/util-arn-parser': 3.310.0 - '@smithy/protocol-http': 1.2.0 - '@smithy/types': 1.2.0 - tslib: 2.6.2 - dev: false - /@aws-sdk/middleware-sdk-sts@3.363.0: - resolution: {integrity: sha512-1yy2Ac50FO8BrODaw5bPWvVrRhaVLqXTFH6iHB+dJLPUkwtY5zLM3Mp+9Ilm7kME+r7oIB1wuO6ZB1Lf4ZszIw==} + '@aws-sdk/middleware-ssec@3.537.0': + resolution: {integrity: sha512-2QWMrbwd5eBy5KCYn9a15JEWBgrK2qFEKQN2lqb/6z0bhtevIOxIRfC99tzvRuPt6nixFQ+ynKuBjcfT4ZFrdQ==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-sdk/middleware-signing': 3.363.0 - '@aws-sdk/types': 3.357.0 - '@smithy/types': 1.2.0 - tslib: 2.6.2 - dev: false - /@aws-sdk/middleware-signing@3.363.0: - resolution: {integrity: sha512-/7qia715pt9JKYIPDGu22WmdZxD8cfF/5xB+1kmILg7ZtjO0pPuTaCNJ7xiIuFd7Dn7JXp5lop08anX/GOhNRQ==} + '@aws-sdk/middleware-user-agent@3.540.0': + resolution: {integrity: sha512-8Rd6wPeXDnOYzWj1XCmOKcx/Q87L0K1/EHqOBocGjLVbN3gmRxBvpmR1pRTjf7IsWfnnzN5btqtcAkfDPYQUMQ==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-sdk/types': 3.357.0 - '@smithy/property-provider': 1.2.0 - '@smithy/protocol-http': 1.2.0 - '@smithy/signature-v4': 1.1.0 - '@smithy/types': 1.2.0 - '@smithy/util-middleware': 1.1.0 - tslib: 2.6.2 - dev: false - /@aws-sdk/middleware-ssec@3.363.0: - resolution: {integrity: sha512-pN+QN1rMShYpJnTJSCIYnNRhD0S8xSZsTn6ThgcO559Xiwz5LMHFOfOXUCEyxtbVW5kMHLUh3w101AMUKae99A==} + '@aws-sdk/middleware-user-agent@3.567.0': + resolution: {integrity: sha512-a7DBGMRBLWJU3BqrQjOtKS4/RcCh/BhhKqwjCE0FEhhm6A/GGuAs/DcBGOl6Y8Wfsby3vejSlppTLH/qtV1E9w==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/region-config-resolver@3.535.0': + resolution: {integrity: sha512-IXOznDiaItBjsQy4Fil0kzX/J3HxIOknEphqHbOfUf+LpA5ugcsxuQQONrbEQusCBnfJyymrldBvBhFmtlU9Wg==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-sdk/types': 3.357.0 - '@smithy/types': 1.2.0 - tslib: 2.6.2 - dev: false - /@aws-sdk/middleware-user-agent@3.363.0: - resolution: {integrity: sha512-ri8YaQvXP6odteVTMfxPqFR26Q0h9ejtqhUDv47P34FaKXedEM4nC6ix6o+5FEYj6l8syGyktftZ5O70NoEhug==} + '@aws-sdk/region-config-resolver@3.567.0': + resolution: {integrity: sha512-VMDyYi5Dh2NydDiIARZ19DwMfbyq0llS736cp47qopmO6wzdeul7WRTx8NKfEYN0/AwEaqmTW0ohx58jSB1lYg==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.556.0': + resolution: {integrity: sha512-bWDSK0ggK7QzAOmPZGv29UAIZocL1MNY7XyOvm3P3P1U3tFMoIBilQQBLabXyHoZ9J3Ik0Vv4n95htUhRQ35ow==} engines: {node: '>=14.0.0'} - dependencies: - '@aws-sdk/types': 3.357.0 - '@aws-sdk/util-endpoints': 3.357.0 - '@smithy/protocol-http': 1.2.0 - '@smithy/types': 1.2.0 - tslib: 2.6.2 - dev: false - /@aws-sdk/signature-v4-multi-region@3.363.0: - resolution: {integrity: sha512-iWamQSpaBKg88LKuiUq8xO/7iyxJ+ORkA3qDhAwUqyTJOg87ma47yFf4ycCKqINnflc3AIGLGzBHnkBc4cMF5g==} + '@aws-sdk/token-providers@3.565.0': + resolution: {integrity: sha512-QPoQUTWijvFZD+7yqu9oJORG6FxqUseD4uhV3iZKVZsj7/Rlpvlh8oEZVCrcnsZ17vKzy+RMUVlnj3vf7Pwp8Q==} engines: {node: '>=14.0.0'} peerDependencies: - '@aws-sdk/signature-v4-crt': ^3.118.0 + '@aws-sdk/client-sso-oidc': ^3.565.0 + + '@aws-sdk/token-providers@3.568.0': + resolution: {integrity: sha512-mCQElYzY5N2JlXB7LyjOoLvRN/JiSV+E9szLwhYN3dleTUCMbGqWb7RiAR2V3fO+mz8f9kR7DThTExKJbKogKw==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sso-oidc': ^3.568.0 + + '@aws-sdk/types@3.535.0': + resolution: {integrity: sha512-aY4MYfduNj+sRR37U7XxYR8wemfbKP6lx00ze2M2uubn7mZotuVrWYAafbMSXrdEMSToE5JDhr28vArSOoLcSg==} + engines: {node: '>=14.0.0'} + + '@aws-sdk/types@3.567.0': + resolution: {integrity: sha512-JBznu45cdgQb8+T/Zab7WpBmfEAh77gsk99xuF4biIb2Sw1mdseONdoGDjEJX57a25TzIv/WUJ2oABWumckz1A==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/util-arn-parser@3.535.0': + resolution: {integrity: sha512-smVo29nUPAOprp8Z5Y3GHuhiOtw6c8/EtLCm5AVMtRsTPw4V414ZXL2H66tzmb5kEeSzQlbfBSBEdIFZoxO9kg==} + engines: {node: '>=14.0.0'} + + '@aws-sdk/util-endpoints@3.540.0': + resolution: {integrity: sha512-1kMyQFAWx6f8alaI6UT65/5YW/7pDWAKAdNwL6vuJLea03KrZRX3PMoONOSJpAS5m3Ot7HlWZvf3wZDNTLELZw==} + engines: {node: '>=14.0.0'} + + '@aws-sdk/util-endpoints@3.567.0': + resolution: {integrity: sha512-WVhot3qmi0BKL9ZKnUqsvCd++4RF2DsJIG32NlRaml1FT9KaqSzNv0RXeA6k/kYwiiNT7y3YWu3Lbzy7c6vG9g==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/util-format-url@3.535.0': + resolution: {integrity: sha512-ElbNkm0bddu53CuW44Iuux1ZbTV50fydbSh/4ypW3LrmUvHx193ogj0HXQ7X26kmmo9rXcsrLdM92yIeTjidVg==} + engines: {node: '>=14.0.0'} + + '@aws-sdk/util-locate-window@3.568.0': + resolution: {integrity: sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/util-user-agent-browser@3.535.0': + resolution: {integrity: sha512-RWMcF/xV5n+nhaA/Ff5P3yNP3Kur/I+VNZngog4TEs92oB/nwOdAg/2JL8bVAhUbMrjTjpwm7PItziYFQoqyig==} + + '@aws-sdk/util-user-agent-browser@3.567.0': + resolution: {integrity: sha512-cqP0uXtZ7m7hRysf3fRyJwcY1jCgQTpJy7BHB5VpsE7DXlXHD5+Ur5L42CY7UrRPrB6lc6YGFqaAOs5ghMcLyA==} + + '@aws-sdk/util-user-agent-node@3.535.0': + resolution: {integrity: sha512-dRek0zUuIT25wOWJlsRm97nTkUlh1NDcLsQZIN2Y8KxhwoXXWtJs5vaDPT+qAg+OpcNj80i1zLR/CirqlFg/TQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + + '@aws-sdk/util-user-agent-node@3.568.0': + resolution: {integrity: sha512-NVoZoLnKF+eXPBvXg+KqixgJkPSrerR6Gqmbjwqbv14Ini+0KNKB0/MXas1mDGvvEgtNkHI/Cb9zlJ3KXpti2A==} + engines: {node: '>=16.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + + '@aws-sdk/util-utf8-browser@3.259.0': + resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} + + '@aws-sdk/xml-builder@3.535.0': + resolution: {integrity: sha512-VXAq/Jz8KIrU84+HqsOJhIKZqG0PNTdi6n6PFQ4xJf44ZQHD/5C7ouH4qCFX5XgZXcgbRIcMVVYGC6Jye0dRng==} + engines: {node: '>=14.0.0'} + + '@babel/code-frame@7.24.2': + resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.24.4': + resolution: {integrity: sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.24.5': + resolution: {integrity: sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.24.5': + resolution: {integrity: sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.23.6': + resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-environment-visitor@7.22.20': + resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-function-name@7.23.0': + resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-hoist-variables@7.22.5': + resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.24.3': + resolution: {integrity: sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.24.5': + resolution: {integrity: sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.24.5': + resolution: {integrity: sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-simple-access@7.24.5': + resolution: {integrity: sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-split-export-declaration@7.24.5': + resolution: {integrity: sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.24.1': + resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.24.5': + resolution: {integrity: sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.23.5': + resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.24.5': + resolution: {integrity: sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==} + engines: {node: '>=6.9.0'} + + '@babel/highlight@7.24.5': + resolution: {integrity: sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.24.5': + resolution: {integrity: sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.24.1': + resolution: {integrity: sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.24.1': + resolution: {integrity: sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime-corejs3@7.24.5': + resolution: {integrity: sha512-GWO0mgzNMLWaSYM4z4NVIuY0Cd1fl8cPnuetuddu5w/qGuvt5Y7oUi/kvvQGK9xgOkFJDQX2heIvTRn/OQ1XTg==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.24.0': + resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.24.5': + resolution: {integrity: sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.24.5': + resolution: {integrity: sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@breejs/later@4.2.0': + resolution: {integrity: sha512-EVMD0SgJtOuFeg0lAVbCwa+qeTKILb87jqvLyUtQswGD9+ce2nB52Y5zbTF1Hc0MDFfbydcMcxb47jSdhikVHA==} + engines: {node: '>= 10'} + + '@cdktf/hcl2json@0.20.7': + resolution: {integrity: sha512-325Swm3ySUEbscSIXrtrNOt0mJCyVTheD5SNuDTcMYLyTPQNgu/6LgKu36YQt0AKK3zUp+f56pEYMitpR9FmLQ==} + + '@colors/colors@1.5.0': + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@eslint-community/eslint-utils@4.4.0': + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.10.0': + resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.0': + resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@gwhitney/detect-indent@7.0.1': + resolution: {integrity: sha512-7bQW+gkKa2kKZPeJf6+c6gFK9ARxQfn+FKy9ScTBppyKRWH2KzsmweXUoklqeEiHiNVWaeP5csIdsNq6w7QhzA==} + engines: {node: '>=12.20'} + + '@humanwhocodes/config-array@0.11.14': + resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} + engines: {node: '>=10.10.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + + '@hyrious/marshal@0.3.3': + resolution: {integrity: sha512-Sprz5CmX+V5MEbgOfXB0iqJS2i703RsV2cXSKC3++Y+4EeUvZPJlv0tgvoBRNT7mvb6aUu7UeOzfiowXlAOmew==} + engines: {node: ^14.18.0 || >=16.0.0} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jest/console@29.7.0': + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/core@29.7.0': + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + 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 + + '@jest/environment@29.7.0': + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect-utils@29.4.1': + resolution: {integrity: sha512-w6YJMn5DlzmxjO00i9wu2YSozUYRBhIoJ6nQwpMYcBMtiqMGJm1QBzOf6DDgRao8dbtpDoaqLg6iiQTvv0UHhQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect@29.7.0': + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/fake-timers@29.7.0': + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/globals@29.7.0': + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/reporters@29.7.0': + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: - '@aws-sdk/signature-v4-crt': + node-notifier: optional: true + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/source-map@29.6.3': + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-result@29.7.0': + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-sequencer@29.7.0': + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/transform@29.7.0': + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.4.15': + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@jsonjoy.com/base64@1.1.1': + resolution: {integrity: sha512-LnFjVChaGY8cZVMwAIMjvA1XwQjZ/zIXHyh28IyJkyNkzof4Dkm1+KN9UIm3lHhREH4vs7XwZ0NpkZKnwOtEfg==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/json-pack@1.0.3': + resolution: {integrity: sha512-Q0SPAdmK6s5Fe3e1kcNvwNyk6e2+CxM8XZdGbf4abZG7nUO05KSie3/iX29loTBuY+75uVP6RixDSPVpotfzmQ==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/util@1.1.2': + resolution: {integrity: sha512-HOGa9wtE6LEz2I5mMQ2pMSjth85PmD71kPbsecs02nEUq3/Kw0wRK3gmZn5BCEB8mFLXByqPxjHgApoMwIPMKQ==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@kwsites/file-exists@1.1.1': + resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} + + '@kwsites/promise-deferred@1.1.1': + resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} + + '@ls-lint/ls-lint@2.2.3': + resolution: {integrity: sha512-ekM12jNm/7O2I/hsRv9HvYkRdfrHpiV1epVuI2NP+eTIcEgdIdKkKCs9KgQydu/8R5YXTov9aHdOgplmCHLupw==} + cpu: [x64, arm64, s390x] + os: [darwin, linux, win32] + hasBin: true + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@npmcli/agent@2.2.2': + resolution: {integrity: sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==} + engines: {node: ^16.14.0 || >=18.0.0} + + '@npmcli/fs@3.1.0': + resolution: {integrity: sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + '@octokit/auth-token@4.0.0': + resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==} + engines: {node: '>= 18'} + + '@octokit/core@5.2.0': + resolution: {integrity: sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==} + engines: {node: '>= 18'} + + '@octokit/endpoint@9.0.5': + resolution: {integrity: sha512-ekqR4/+PCLkEBF6qgj8WqJfvDq65RH85OAgrtnVp1mSxaXF03u2xW/hUdweGS5654IlC0wkNYC18Z50tSYTAFw==} + engines: {node: '>= 18'} + + '@octokit/graphql@7.1.0': + resolution: {integrity: sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==} + engines: {node: '>= 18'} + + '@octokit/openapi-types@20.0.0': + resolution: {integrity: sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==} + + '@octokit/openapi-types@22.2.0': + resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==} + + '@octokit/plugin-paginate-rest@11.3.1': + resolution: {integrity: sha512-ryqobs26cLtM1kQxqeZui4v8FeznirUsksiA+RYemMPJ7Micju0WSkv50dBksTuZks9O5cg4wp+t8fZ/cLY56g==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '5' + + '@octokit/plugin-paginate-rest@9.2.1': + resolution: {integrity: sha512-wfGhE/TAkXZRLjksFXuDZdmGnJQHvtU/joFQdweXUgzo1XwvBCD4o4+75NtFfjfLK5IwLf9vHTfSiU3sLRYpRw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '5' + + '@octokit/plugin-request-log@4.0.1': + resolution: {integrity: sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '5' + + '@octokit/plugin-rest-endpoint-methods@13.2.2': + resolution: {integrity: sha512-EI7kXWidkt3Xlok5uN43suK99VWqc8OaIMktY9d9+RNKl69juoTyxmLoWPIZgJYzi41qj/9zU7G/ljnNOJ5AFA==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': ^5 + + '@octokit/plugin-retry@6.0.1': + resolution: {integrity: sha512-SKs+Tz9oj0g4p28qkZwl/topGcb0k0qPNX/i7vBKmDsjoeqnVfFUquqrE/O9oJY7+oLzdCtkiWSXLpLjvl6uog==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=5' + + '@octokit/plugin-throttling@8.2.0': + resolution: {integrity: sha512-nOpWtLayKFpgqmgD0y3GqXafMFuKcA4tRPZIfu7BArd2lEZeb1988nhWhwx4aZWmjDmUfdgVf7W+Tt4AmvRmMQ==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': ^5.0.0 + + '@octokit/request-error@5.1.0': + resolution: {integrity: sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==} + engines: {node: '>= 18'} + + '@octokit/request@8.4.0': + resolution: {integrity: sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==} + engines: {node: '>= 18'} + + '@octokit/rest@20.1.1': + resolution: {integrity: sha512-MB4AYDsM5jhIHro/dq4ix1iWTLGToIGk6cWF5L6vanFaMble5jTX/UBQyiv05HsWnwUtY8JrfHy2LWfKwihqMw==} + engines: {node: '>= 18'} + + '@octokit/types@12.6.0': + resolution: {integrity: sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==} + + '@octokit/types@13.5.0': + resolution: {integrity: sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==} + + '@one-ini/wasm@0.1.1': + resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + + '@openpgp/web-stream-tools@0.0.14': + resolution: {integrity: sha512-6btCNVf6YSsmlyIS7yw+IbzXeXCEcJxeSpxvSxkDuZj9B/ekt4fXkZj4oOaIxG4SKTftIK1svnlVroJ1cCMT4g==} + peerDependencies: + typescript: '>=4.2' + peerDependenciesMeta: + typescript: + optional: true + + '@opentelemetry/api-logs@0.51.1': + resolution: {integrity: sha512-E3skn949Pk1z2XtXu/lxf6QAZpawuTM/IUEXcAzpiUkTd73Hmvw26FiN3cJuTmkpM5hZzHwkomVdtrh/n/zzwA==} + engines: {node: '>=14'} + + '@opentelemetry/api@1.8.0': + resolution: {integrity: sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/context-async-hooks@1.24.1': + resolution: {integrity: sha512-R5r6DO4kgEOVBxFXhXjwospLQkv+sYxwCfjvoZBe7Zm6KKXAV9kDSJhi/D1BweowdZmO+sdbENLs374gER8hpQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.9.0' + + '@opentelemetry/core@1.24.1': + resolution: {integrity: sha512-wMSGfsdmibI88K9wB498zXY04yThPexo8jvwNNlm542HZB7XrrMRBbAyKJqG8qDRJwIBdBrPMi4V9ZPW/sqrcg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.9.0' + + '@opentelemetry/exporter-trace-otlp-http@0.51.1': + resolution: {integrity: sha512-n+LhLPsX07URh+HhV2SHVSvz1t4G/l/CE5BjpmhAPqeTceFac1VpyQkavWEJbvnK5bUEXijWt4LxAxFpt2fXyw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/instrumentation-bunyan@0.38.0': + resolution: {integrity: sha512-ThNcgTE22W7PKzTzz5qfGxb5Gf7rA3EORousYo2nJWHHcF6gqiMNv2+GXY3MdpjLBr8IgCfhtvbQdD6rlIPUpA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-http@0.51.1': + resolution: {integrity: sha512-6b3nZnFFEz/3xZ6w8bVxctPUWIPWiXuPQ725530JgxnN1cvYFd8CJ75PrHZNjynmzSSnqBkN3ef4R9N+RpMh8Q==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.51.1': + resolution: {integrity: sha512-JIrvhpgqY6437QIqToyozrUG1h5UhwHkaGK/WAX+fkrpyPtc+RO5FkRtUd9BH0MibabHHvqsnBGKfKVijbmp8w==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.51.1': + resolution: {integrity: sha512-UYlnOYyDdzo1Gw559EHCzru0RwhvuXCwoH8jGo9J4gO1TE58GjnEmIjomMsKBCym3qWNJfIQXw+9SZCV0DdQNg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/otlp-transformer@0.51.1': + resolution: {integrity: sha512-OppYOXwV9LQqqtYUCywqoOqX/JT9LQ5/FMuPZ//eTkvuHdUC4ZMwz2c6uSoT2R90GWvvGnF1iEqTGyTT3xAt2Q==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.9.0' + + '@opentelemetry/propagator-b3@1.24.1': + resolution: {integrity: sha512-nda97ZwhpZKyUJTXqQuKzNhPMUgMLunbbGWn8kroBwegn+nh6OhtyGkrVQsQLNdVKJl0KeB5z0ZgeWszrYhwFw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.9.0' + + '@opentelemetry/propagator-jaeger@1.24.1': + resolution: {integrity: sha512-7bRBJn3FG1l195A1m+xXRHvgzAOBsfmRi9uZ5Da18oTh7BLmNDiA8+kpk51FpTsU1PCikPVpRDNPhKVB6lyzZg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.9.0' + + '@opentelemetry/resources@1.24.1': + resolution: {integrity: sha512-cyv0MwAaPF7O86x5hk3NNgenMObeejZFLJJDVuSeSMIsknlsj3oOZzRv3qSzlwYomXsICfBeFFlxwHQte5mGXQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.9.0' + + '@opentelemetry/sdk-logs@0.51.1': + resolution: {integrity: sha512-ULQQtl82b673PpZc5/0EtH4V+BrwVOgKJZEB7tYZnGTG3I98tQVk89S9/JSixomDr++F4ih+LSJTCqIKBz+MQQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.9.0' + '@opentelemetry/api-logs': '>=0.39.1' + + '@opentelemetry/sdk-metrics@1.24.1': + resolution: {integrity: sha512-FrAqCbbGao9iKI+Mgh+OsC9+U2YMoXnlDHe06yH7dvavCKzE3S892dGtX54+WhSFVxHR/TMRVJiK/CV93GR0TQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.9.0' + + '@opentelemetry/sdk-trace-base@1.24.1': + resolution: {integrity: sha512-zz+N423IcySgjihl2NfjBf0qw1RWe11XIAWVrTNOSSI6dtSPJiVom2zipFB2AEEtJWpv0Iz6DY6+TjnyTV5pWg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.9.0' + + '@opentelemetry/sdk-trace-node@1.24.1': + resolution: {integrity: sha512-/FZX8uWaGIAwsDhqI8VvQ+qWtfMNlXjaFYGc+vmxgdRFppCSSIRwrPyIhJO1qx61okyYhoyxVEZAfoiNxrfJCg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.9.0' + + '@opentelemetry/semantic-conventions@1.24.1': + resolution: {integrity: sha512-VkliWlS4/+GHLLW7J/rVBA00uXus1SWvwFvcUDxDwmFxYfg/2VI6ekwdXS28cjI8Qz2ky2BzG8OUHo+WeYIWqw==} + engines: {node: '>=14'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@pnpm/config.env-replace@1.1.0': + resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} + engines: {node: '>=12.22.0'} + + '@pnpm/constants@6.1.0': + resolution: {integrity: sha512-L6AiU3OXv9kjKGTJN9j8n1TeJGDcLX9atQlZvAkthlvbXjvKc5SKNWESc/eXhr5nEfuMWhQhiKHDJCpYejmeCQ==} + engines: {node: '>=14.19'} + + '@pnpm/error@4.0.0': + resolution: {integrity: sha512-NI4DFCMF6xb1SA0bZiiV5KrMCaJM2QmPJFC6p78FXujn7FpiRSWhT9r032wpuQumsl7DEmN4s3wl/P8TA+bL8w==} + engines: {node: '>=14.6'} + + '@pnpm/graceful-fs@2.0.0': + resolution: {integrity: sha512-ogUZCGf0/UILZt6d8PsO4gA4pXh7f0BumXeFkcCe4AQ65PXPKfAkHC0C30Lheh2EgFOpLZm3twDP1Eiww18gew==} + engines: {node: '>=14.19'} + + '@pnpm/network.ca-file@1.0.2': + resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} + engines: {node: '>=12.22.0'} + + '@pnpm/npm-conf@2.2.2': + resolution: {integrity: sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==} + engines: {node: '>=12'} + + '@pnpm/read-project-manifest@4.1.1': + resolution: {integrity: sha512-jGNoofG8kkUlgAMX8fqbUwRRXYf4WcWdvi/y1Sv1abUfcoVgXW6GdGVm0MIJ+enaong3hXHjaLl/AwmSj6O1Uw==} + engines: {node: '>=14.6'} + + '@pnpm/text.comments-parser@1.0.0': + resolution: {integrity: sha512-iG0qrFcObze3uK+HligvzaTocZKukqqIj1dC3NOH58NeMACUW1NUitSKBgeWuNIE4LJT3SPxnyLEBARMMcqVKA==} + engines: {node: '>=14.6'} + + '@pnpm/types@8.9.0': + resolution: {integrity: sha512-3MYHYm8epnciApn6w5Fzx6sepawmsNU7l6lvIq+ER22/DPSrr83YMhU/EQWnf4lORn2YyiXFj0FJSyJzEtIGmw==} + engines: {node: '>=14.6'} + + '@pnpm/util.lex-comparator@1.0.0': + resolution: {integrity: sha512-3aBQPHntVgk5AweBWZn+1I/fqZ9krK/w01197aYVkAJQGftb+BVWgEepxY5GChjSW12j52XX+CmfynYZ/p0DFQ==} + engines: {node: '>=12.22.0'} + + '@pnpm/write-project-manifest@4.1.1': + resolution: {integrity: sha512-nRqvPYO8xUVdgy/KhJuaCrWlVT/4uZr97Mpbuizsa6CmvtCQf3NuYnVvOOrpYiKUJcZYtEvm84OooJ8+lJytMQ==} + engines: {node: '>=14.6'} + + '@qnighy/marshal@0.1.3': + resolution: {integrity: sha512-uaDZTJYtD2UgQTGemmgWeth+e2WapZm+GkAq8UU8AJ55PKRFaf1GkH7X/uzA+Ygu8iInzIlM2FGyCUnruyMKMg==} + + '@redis/bloom@1.2.0': + resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/client@1.5.14': + resolution: {integrity: sha512-YGn0GqsRBFUQxklhY7v562VMOP0DcmlrHHs3IV1mFE3cbxe31IITUkqhBcIhVSI/2JqtWAJXg5mjV4aU+zD0HA==} + engines: {node: '>=14'} + + '@redis/graph@1.1.1': + resolution: {integrity: sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/json@1.0.6': + resolution: {integrity: sha512-rcZO3bfQbm2zPRpqo82XbW8zg4G/w4W3tI7X8Mqleq9goQjAGLL7q/1n1ZX4dXEAmORVZ4s1+uKLaUOg7LrUhw==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/search@1.1.6': + resolution: {integrity: sha512-mZXCxbTYKBQ3M2lZnEddwEAks0Kc7nauire8q20oA0oA/LoA+E/b5Y5KZn232ztPb1FkIGqo12vh3Lf+Vw5iTw==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/time-series@1.0.5': + resolution: {integrity: sha512-IFjIgTusQym2B5IZJG3XKr5llka7ey84fw/NOYqESP5WUfQs9zz1ww/9+qoz4ka/S6KcGBodzlCeZ5UImKbscg==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@renovatebot/eslint-plugin@file:tools/eslint': + resolution: {directory: tools/eslint, type: directory} + + '@renovatebot/kbpgp@3.0.1': + resolution: {integrity: sha512-n78K03XvVIVhE95Thlmq+AXl6j9gYKnsKtrVzU7vnmsKNQDSPn8zTRs1wXGjjdup9REPmqRNcITeq3NsG32QYQ==} + engines: {node: ^18.12.0 || >=20.9.0, pnpm: ^9.0.0} + + '@renovatebot/osv-offline-db@1.6.0': + resolution: {integrity: sha512-cEOCTyd3+/7gPDmBn0pyJtF01+f9e/dJ1mOoML+v5AsP8GIPAzhtQUuIB5FiCxS4IsbP0qm34anYUZHGJldNJA==} + + '@renovatebot/osv-offline@1.5.5': + resolution: {integrity: sha512-jZYVZww5l9+4Xaa+wS/Hc+zsezO229tEoorhBa+buseKMUtYQxNI0RAc/n2AuWYjqWYr4qL5rNg+QcMmwqj/Kg==} + + '@renovatebot/pep440@3.0.20': + resolution: {integrity: sha512-Jw8jzHh2r1LAPTrjQlIwh/+8J3N2MqXZgPuTt6HdNeJIBjJskV8bsEfGs9rBzXi/omeHob3BXnvlECu2rCCUYw==} + engines: {node: ^18.12.0 || >= 20.0.0, pnpm: ^8.6.11} + + '@renovatebot/ruby-semver@3.0.23': + resolution: {integrity: sha512-YGvsvvyxOgv5Uq+sFEdD1yviyrPGs9hocjhIo7uWTj/EAIlbGyk5YA5JrHql3EkJf0tVsyfmEkM3kLK+45hmIw==} + engines: {node: ^18.12.0 || >= 20.0.0, pnpm: ^8.6.11} + + '@seald-io/binary-search-tree@1.0.3': + resolution: {integrity: sha512-qv3jnwoakeax2razYaMsGI/luWdliBLHTdC6jU55hQt1hcFqzauH/HsBollQ7IR4ySTtYhT+xyHoijpA16C+tA==} + + '@seald-io/nedb@4.0.4': + resolution: {integrity: sha512-CUNcMio7QUHTA+sIJ/DC5JzVNNsHe743TPmC4H5Gij9zDLMbmrCT2li3eVB72/gF63BPS8pWEZrjlAMRKA8FDw==} + + '@semantic-release/commit-analyzer@11.1.0': + resolution: {integrity: sha512-cXNTbv3nXR2hlzHjAMgbuiQVtvWHTlwwISt60B+4NZv01y/QRY7p2HcJm8Eh2StzcTJoNnflvKjHH/cjFS7d5g==} + engines: {node: ^18.17 || >=20.6.1} + peerDependencies: + semantic-release: '>=20.1.0' + + '@semantic-release/error@3.0.0': + resolution: {integrity: sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==} + engines: {node: '>=14.17'} + + '@semantic-release/error@4.0.0': + resolution: {integrity: sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==} + engines: {node: '>=18'} + + '@semantic-release/exec@6.0.3': + resolution: {integrity: sha512-bxAq8vLOw76aV89vxxICecEa8jfaWwYITw6X74zzlO0mc/Bgieqx9kBRz9z96pHectiTAtsCwsQcUyLYWnp3VQ==} + engines: {node: '>=14.17'} + peerDependencies: + semantic-release: '>=18.0.0' + + '@semantic-release/github@9.2.6': + resolution: {integrity: sha512-shi+Lrf6exeNZF+sBhK+P011LSbhmIAoUEgEY6SsxF8irJ+J2stwI5jkyDQ+4gzYyDImzV6LCKdYB9FXnQRWKA==} + engines: {node: '>=18'} + peerDependencies: + semantic-release: '>=20.1.0' + + '@semantic-release/npm@11.0.3': + resolution: {integrity: sha512-KUsozQGhRBAnoVg4UMZj9ep436VEGwT536/jwSqB7vcEfA6oncCUU7UIYTRdLx7GvTtqn0kBjnkfLVkcnBa2YQ==} + engines: {node: ^18.17 || >=20} + peerDependencies: + semantic-release: '>=20.1.0' + + '@semantic-release/release-notes-generator@12.1.0': + resolution: {integrity: sha512-g6M9AjUKAZUZnxaJZnouNBeDNTCUrJ5Ltj+VJ60gJeDaRRahcHsry9HW8yKrnKkKNkx5lbWiEP1FPMqVNQz8Kg==} + engines: {node: ^18.17 || >=20.6.1} + peerDependencies: + semantic-release: '>=20.1.0' + + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + + '@sindresorhus/is@4.6.0': + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + + '@sindresorhus/merge-streams@2.3.0': + resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} + engines: {node: '>=18'} + + '@sinonjs/commons@2.0.0': + resolution: {integrity: sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@10.3.0': + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + + '@sinonjs/fake-timers@11.2.2': + resolution: {integrity: sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==} + + '@sinonjs/samsam@8.0.0': + resolution: {integrity: sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==} + + '@sinonjs/text-encoding@0.7.2': + resolution: {integrity: sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==} + + '@smithy/abort-controller@2.2.0': + resolution: {integrity: sha512-wRlta7GuLWpTqtFfGo+nZyOO1vEvewdNR1R4rTxpC8XU6vG/NDyrFBhwLZsqg1NUoR1noVaXJPC/7ZK47QCySw==} + engines: {node: '>=14.0.0'} + + '@smithy/chunked-blob-reader-native@2.2.0': + resolution: {integrity: sha512-VNB5+1oCgX3Fzs072yuRsUoC2N4Zg/LJ11DTxX3+Qu+Paa6AmbIF0E9sc2wthz9Psrk/zcOlTCyuposlIhPjZQ==} + + '@smithy/chunked-blob-reader@2.2.0': + resolution: {integrity: sha512-3GJNvRwXBGdkDZZOGiziVYzDpn4j6zfyULHMDKAGIUo72yHALpE9CbhfQp/XcLNVoc1byfMpn6uW5H2BqPjgaQ==} + + '@smithy/config-resolver@2.2.0': + resolution: {integrity: sha512-fsiMgd8toyUba6n1WRmr+qACzXltpdDkPTAaDqc8QqPBUzO+/JKwL6bUBseHVi8tu9l+3JOK+tSf7cay+4B3LA==} + engines: {node: '>=14.0.0'} + + '@smithy/core@1.4.2': + resolution: {integrity: sha512-2fek3I0KZHWJlRLvRTqxTEri+qV0GRHrJIoLFuBMZB4EMg4WgeBGfF0X6abnrNYpq55KJ6R4D6x4f0vLnhzinA==} + engines: {node: '>=14.0.0'} + + '@smithy/credential-provider-imds@2.3.0': + resolution: {integrity: sha512-BWB9mIukO1wjEOo1Ojgl6LrG4avcaC7T/ZP6ptmAaW4xluhSIPZhY+/PI5YKzlk+jsm+4sQZB45Bt1OfMeQa3w==} + engines: {node: '>=14.0.0'} + + '@smithy/eventstream-codec@2.2.0': + resolution: {integrity: sha512-8janZoJw85nJmQZc4L8TuePp2pk1nxLgkxIR0TUjKJ5Dkj5oelB9WtiSSGXCQvNsJl0VSTvK/2ueMXxvpa9GVw==} + + '@smithy/eventstream-serde-browser@2.2.0': + resolution: {integrity: sha512-UaPf8jKbcP71BGiO0CdeLmlg+RhWnlN8ipsMSdwvqBFigl5nil3rHOI/5GE3tfiuX8LvY5Z9N0meuU7Rab7jWw==} + engines: {node: '>=14.0.0'} + + '@smithy/eventstream-serde-config-resolver@2.2.0': + resolution: {integrity: sha512-RHhbTw/JW3+r8QQH7PrganjNCiuiEZmpi6fYUAetFfPLfZ6EkiA08uN3EFfcyKubXQxOwTeJRZSQmDDCdUshaA==} + engines: {node: '>=14.0.0'} + + '@smithy/eventstream-serde-node@2.2.0': + resolution: {integrity: sha512-zpQMtJVqCUMn+pCSFcl9K/RPNtQE0NuMh8sKpCdEHafhwRsjP50Oq/4kMmvxSRy6d8Jslqd8BLvDngrUtmN9iA==} + engines: {node: '>=14.0.0'} + + '@smithy/eventstream-serde-universal@2.2.0': + resolution: {integrity: sha512-pvoe/vvJY0mOpuF84BEtyZoYfbehiFj8KKWk1ds2AT0mTLYFVs+7sBJZmioOFdBXKd48lfrx1vumdPdmGlCLxA==} + engines: {node: '>=14.0.0'} + + '@smithy/fetch-http-handler@2.5.0': + resolution: {integrity: sha512-BOWEBeppWhLn/no/JxUL/ghTfANTjT7kg3Ww2rPqTUY9R4yHPXxJ9JhMe3Z03LN3aPwiwlpDIUcVw1xDyHqEhw==} + + '@smithy/hash-blob-browser@2.2.0': + resolution: {integrity: sha512-SGPoVH8mdXBqrkVCJ1Hd1X7vh1zDXojNN1yZyZTZsCno99hVue9+IYzWDjq/EQDDXxmITB0gBmuyPh8oAZSTcg==} + + '@smithy/hash-node@2.2.0': + resolution: {integrity: sha512-zLWaC/5aWpMrHKpoDF6nqpNtBhlAYKF/7+9yMN7GpdR8CzohnWfGtMznPybnwSS8saaXBMxIGwJqR4HmRp6b3g==} + engines: {node: '>=14.0.0'} + + '@smithy/hash-stream-node@2.2.0': + resolution: {integrity: sha512-aT+HCATOSRMGpPI7bi7NSsTNVZE/La9IaxLXWoVAYMxHT5hGO3ZOGEMZQg8A6nNL+pdFGtZQtND1eoY084HgHQ==} + engines: {node: '>=14.0.0'} + + '@smithy/invalid-dependency@2.2.0': + resolution: {integrity: sha512-nEDASdbKFKPXN2O6lOlTgrEEOO9NHIeO+HVvZnkqc8h5U9g3BIhWsvzFo+UcUbliMHvKNPD/zVxDrkP1Sbgp8Q==} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + + '@smithy/md5-js@2.2.0': + resolution: {integrity: sha512-M26XTtt9IIusVMOWEAhIvFIr9jYj4ISPPGJROqw6vXngO3IYJCnVVSMFn4Tx1rUTG5BiKJNg9u2nxmBiZC5IlQ==} + + '@smithy/middleware-content-length@2.2.0': + resolution: {integrity: sha512-5bl2LG1Ah/7E5cMSC+q+h3IpVHMeOkG0yLRyQT1p2aMJkSrZG7RlXHPuAgb7EyaFeidKEnnd/fNaLLaKlHGzDQ==} + engines: {node: '>=14.0.0'} + + '@smithy/middleware-endpoint@2.5.1': + resolution: {integrity: sha512-1/8kFp6Fl4OsSIVTWHnNjLnTL8IqpIb/D3sTSczrKFnrE9VMNWxnrRKNvpUHOJ6zpGD5f62TPm7+17ilTJpiCQ==} + engines: {node: '>=14.0.0'} + + '@smithy/middleware-retry@2.3.1': + resolution: {integrity: sha512-P2bGufFpFdYcWvqpyqqmalRtwFUNUA8vHjJR5iGqbfR6mp65qKOLcUd6lTr4S9Gn/enynSrSf3p3FVgVAf6bXA==} + engines: {node: '>=14.0.0'} + + '@smithy/middleware-serde@2.3.0': + resolution: {integrity: sha512-sIADe7ojwqTyvEQBe1nc/GXB9wdHhi9UwyX0lTyttmUWDJLP655ZYE1WngnNyXREme8I27KCaUhyhZWRXL0q7Q==} + engines: {node: '>=14.0.0'} + + '@smithy/middleware-stack@2.2.0': + resolution: {integrity: sha512-Qntc3jrtwwrsAC+X8wms8zhrTr0sFXnyEGhZd9sLtsJ/6gGQKFzNB+wWbOcpJd7BR8ThNCoKt76BuQahfMvpeA==} + engines: {node: '>=14.0.0'} + + '@smithy/node-config-provider@2.3.0': + resolution: {integrity: sha512-0elK5/03a1JPWMDPaS726Iw6LpQg80gFut1tNpPfxFuChEEklo2yL823V94SpTZTxmKlXFtFgsP55uh3dErnIg==} + engines: {node: '>=14.0.0'} + + '@smithy/node-http-handler@2.5.0': + resolution: {integrity: sha512-mVGyPBzkkGQsPoxQUbxlEfRjrj6FPyA3u3u2VXGr9hT8wilsoQdZdvKpMBFMB8Crfhv5dNkKHIW0Yyuc7eABqA==} + engines: {node: '>=14.0.0'} + + '@smithy/property-provider@2.2.0': + resolution: {integrity: sha512-+xiil2lFhtTRzXkx8F053AV46QnIw6e7MV8od5Mi68E1ICOjCeCHw2XfLnDEUHnT9WGUIkwcqavXjfwuJbGlpg==} + engines: {node: '>=14.0.0'} + + '@smithy/protocol-http@3.3.0': + resolution: {integrity: sha512-Xy5XK1AFWW2nlY/biWZXu6/krgbaf2dg0q492D8M5qthsnU2H+UgFeZLbM76FnH7s6RO/xhQRkj+T6KBO3JzgQ==} + engines: {node: '>=14.0.0'} + + '@smithy/querystring-builder@2.2.0': + resolution: {integrity: sha512-L1kSeviUWL+emq3CUVSgdogoM/D9QMFaqxL/dd0X7PCNWmPXqt+ExtrBjqT0V7HLN03Vs9SuiLrG3zy3JGnE5A==} + engines: {node: '>=14.0.0'} + + '@smithy/querystring-parser@2.2.0': + resolution: {integrity: sha512-BvHCDrKfbG5Yhbpj4vsbuPV2GgcpHiAkLeIlcA1LtfpMz3jrqizP1+OguSNSj1MwBHEiN+jwNisXLGdajGDQJA==} + engines: {node: '>=14.0.0'} + + '@smithy/service-error-classification@2.1.5': + resolution: {integrity: sha512-uBDTIBBEdAQryvHdc5W8sS5YX7RQzF683XrHePVdFmAgKiMofU15FLSM0/HU03hKTnazdNRFa0YHS7+ArwoUSQ==} + engines: {node: '>=14.0.0'} + + '@smithy/shared-ini-file-loader@2.4.0': + resolution: {integrity: sha512-WyujUJL8e1B6Z4PBfAqC/aGY1+C7T0w20Gih3yrvJSk97gpiVfB+y7c46T4Nunk+ZngLq0rOIdeVeIklk0R3OA==} + engines: {node: '>=14.0.0'} + + '@smithy/signature-v4@2.3.0': + resolution: {integrity: sha512-ui/NlpILU+6HAQBfJX8BBsDXuKSNrjTSuOYArRblcrErwKFutjrCNb/OExfVRyj9+26F9J+ZmfWT+fKWuDrH3Q==} + engines: {node: '>=14.0.0'} + + '@smithy/smithy-client@2.5.1': + resolution: {integrity: sha512-jrbSQrYCho0yDaaf92qWgd+7nAeap5LtHTI51KXqmpIFCceKU3K9+vIVTUH72bOJngBMqa4kyu1VJhRcSrk/CQ==} + engines: {node: '>=14.0.0'} + + '@smithy/types@2.12.0': + resolution: {integrity: sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==} + engines: {node: '>=14.0.0'} + + '@smithy/url-parser@2.2.0': + resolution: {integrity: sha512-hoA4zm61q1mNTpksiSWp2nEl1dt3j726HdRhiNgVJQMj7mLp7dprtF57mOB6JvEk/x9d2bsuL5hlqZbBuHQylQ==} + + '@smithy/util-base64@2.3.0': + resolution: {integrity: sha512-s3+eVwNeJuXUwuMbusncZNViuhv2LjVJ1nMwTqSA0XAC7gjKhqqxRdJPhR8+YrkoZ9IiIbFk/yK6ACe/xlF+hw==} + engines: {node: '>=14.0.0'} + + '@smithy/util-body-length-browser@2.2.0': + resolution: {integrity: sha512-dtpw9uQP7W+n3vOtx0CfBD5EWd7EPdIdsQnWTDoFf77e3VUf05uA7R7TGipIo8e4WL2kuPdnsr3hMQn9ziYj5w==} + + '@smithy/util-body-length-node@2.3.0': + resolution: {integrity: sha512-ITWT1Wqjubf2CJthb0BuT9+bpzBfXeMokH/AAa5EJQgbv9aPMVfnM76iFIZVFf50hYXGbtiV71BHAthNWd6+dw==} + engines: {node: '>=14.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-config-provider@2.3.0': + resolution: {integrity: sha512-HZkzrRcuFN1k70RLqlNK4FnPXKOpkik1+4JaBoHNJn+RnJGYqaa3c5/+XtLOXhlKzlRgNvyaLieHTW2VwGN0VQ==} + engines: {node: '>=14.0.0'} + + '@smithy/util-defaults-mode-browser@2.2.1': + resolution: {integrity: sha512-RtKW+8j8skk17SYowucwRUjeh4mCtnm5odCL0Lm2NtHQBsYKrNW0od9Rhopu9wF1gHMfHeWF7i90NwBz/U22Kw==} + engines: {node: '>= 10.0.0'} + + '@smithy/util-defaults-mode-node@2.3.1': + resolution: {integrity: sha512-vkMXHQ0BcLFysBMWgSBLSk3+leMpFSyyFj8zQtv5ZyUBx8/owVh1/pPEkzmW/DR/Gy/5c8vjLDD9gZjXNKbrpA==} + engines: {node: '>= 10.0.0'} + + '@smithy/util-endpoints@1.2.0': + resolution: {integrity: sha512-BuDHv8zRjsE5zXd3PxFXFknzBG3owCpjq8G3FcsXW3CykYXuEqM3nTSsmLzw5q+T12ZYuDlVUZKBdpNbhVtlrQ==} + engines: {node: '>= 14.0.0'} + + '@smithy/util-hex-encoding@2.2.0': + resolution: {integrity: sha512-7iKXR+/4TpLK194pVjKiasIyqMtTYJsgKgM242Y9uzt5dhHnUDvMNb+3xIhRJ9QhvqGii/5cRUt4fJn3dtXNHQ==} + engines: {node: '>=14.0.0'} + + '@smithy/util-middleware@2.2.0': + resolution: {integrity: sha512-L1qpleXf9QD6LwLCJ5jddGkgWyuSvWBkJwWAZ6kFkdifdso+sk3L3O1HdmPvCdnCK3IS4qWyPxev01QMnfHSBw==} + engines: {node: '>=14.0.0'} + + '@smithy/util-retry@2.2.0': + resolution: {integrity: sha512-q9+pAFPTfftHXRytmZ7GzLFFrEGavqapFc06XxzZFcSIGERXMerXxCitjOG1prVDR9QdjqotF40SWvbqcCpf8g==} + engines: {node: '>= 14.0.0'} + + '@smithy/util-stream@2.2.0': + resolution: {integrity: sha512-17faEXbYWIRst1aU9SvPZyMdWmqIrduZjVOqCPMIsWFNxs5yQQgFrJL6b2SdiCzyW9mJoDjFtgi53xx7EH+BXA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-uri-escape@2.2.0': + resolution: {integrity: sha512-jtmJMyt1xMD/d8OtbVJ2gFZOSKc+ueYJZPW20ULW1GOp/q/YIM0wNh+u8ZFao9UaIGz4WoPW8hC64qlWLIfoDA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + + '@smithy/util-waiter@2.2.0': + resolution: {integrity: sha512-IHk53BVw6MPMi2Gsn+hCng8rFA3ZmR3Rk7GllxDUW9qFJl/hiSvskn7XldkECapQVkIg/1dHpMAxI9xSTaLLSA==} + engines: {node: '>=14.0.0'} + + '@swc/core-darwin-arm64@1.5.7': + resolution: {integrity: sha512-bZLVHPTpH3h6yhwVl395k0Mtx8v6CGhq5r4KQdAoPbADU974Mauz1b6ViHAJ74O0IVE5vyy7tD3OpkQxL/vMDQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.5.7': + resolution: {integrity: sha512-RpUyu2GsviwTc2qVajPL0l8nf2vKj5wzO3WkLSHAHEJbiUZk83NJrZd1RVbEknIMO7+Uyjh54hEh8R26jSByaw==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.5.7': + resolution: {integrity: sha512-cTZWTnCXLABOuvWiv6nQQM0hP6ZWEkzdgDvztgHI/+u/MvtzJBN5lBQ2lue/9sSFYLMqzqff5EHKlFtrJCA9dQ==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.5.7': + resolution: {integrity: sha512-hoeTJFBiE/IJP30Be7djWF8Q5KVgkbDtjySmvYLg9P94bHg9TJPSQoC72tXx/oXOgXvElDe/GMybru0UxhKx4g==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-arm64-musl@1.5.7': + resolution: {integrity: sha512-+NDhK+IFTiVK1/o7EXdCeF2hEzCiaRSrb9zD7X2Z7inwWlxAntcSuzZW7Y6BRqGQH89KA91qYgwbnjgTQ22PiQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-x64-gnu@1.5.7': + resolution: {integrity: sha512-25GXpJmeFxKB+7pbY7YQLhWWjkYlR+kHz5I3j9WRl3Lp4v4UD67OGXwPe+DIcHqcouA1fhLhsgHJWtsaNOMBNg==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-linux-x64-musl@1.5.7': + resolution: {integrity: sha512-0VN9Y5EAPBESmSPPsCJzplZHV26akC0sIgd3Hc/7S/1GkSMoeuVL+V9vt+F/cCuzr4VidzSkqftdP3qEIsXSpg==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-win32-arm64-msvc@1.5.7': + resolution: {integrity: sha512-RtoNnstBwy5VloNCvmvYNApkTmuCe4sNcoYWpmY7C1+bPR+6SOo8im1G6/FpNem8AR5fcZCmXHWQ+EUmRWJyuA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.5.7': + resolution: {integrity: sha512-Xm0TfvcmmspvQg1s4+USL3x8D+YPAfX2JHygvxAnCJ0EHun8cm2zvfNBcsTlnwYb0ybFWXXY129aq1wgFC9TpQ==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.5.7': + resolution: {integrity: sha512-tp43WfJLCsKLQKBmjmY/0vv1slVywR5Q4qKjF5OIY8QijaEW7/8VwPyUyVoJZEnDgv9jKtUTG5PzqtIYPZGnyg==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.5.7': + resolution: {integrity: sha512-U4qJRBefIJNJDRCCiVtkfa/hpiZ7w0R6kASea+/KLp+vkus3zcLSB8Ub8SvKgTIxjWpwsKcZlPf5nrv4ls46SQ==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': ^0.5.0 + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/types@0.1.7': + resolution: {integrity: sha512-scHWahbHF0eyj3JsxG9CFJgFdFNaVQCNAimBlT6PzS3n/HptxqREjsm4OH6AN3lYcffZYSPxXW8ua2BEHp0lJQ==} + + '@szmarczak/http-timer@4.0.6': + resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} + engines: {node: '>=10'} + + '@thi.ng/api@7.2.0': + resolution: {integrity: sha512-4NcwHXxwPF/JgJG/jSFd9rjfQNguF0QrHvd6e+CEf4T0sFChqetW6ZmJ6/a2X+noDVntgulegA+Bx0HHzw+Tyw==} + + '@thi.ng/arrays@1.0.3': + resolution: {integrity: sha512-ZUB27bdpTwcvxYJTlt/eWKrj98nWXo0lAUPwRwubk4GlH8rTKKkc7qZr9/4LCKPsNjnZdQqbBtNvNf3HjYxCzw==} + + '@thi.ng/checks@2.9.11': + resolution: {integrity: sha512-fBvWod32w24JlJsrrOdl+tlx+UNehCORi4rHaJ7l7HH+SEhD/lYTCXOBjwu9D/ztIUjMP5Q+n8cAqI5iPhbvAQ==} + + '@thi.ng/compare@1.3.34': + resolution: {integrity: sha512-E+UWhmo8l5yeHDuriPUsfrnk/Mj5kSDNRX7lPfv2zNdAQ7N8UDzc0IXu46U6EpqtCReo+2n5N8qzfD3TjerFRw==} + + '@thi.ng/equiv@1.0.45': + resolution: {integrity: sha512-tdXaJfF0pFvT80Q7BOlhc7H7ja/RbVGzlGpE4LqjDWfXPPbLYwmq6EbQuHWeXuvT0qe+BsGnuO5UXAR5B8oGGQ==} + + '@thi.ng/errors@1.3.4': + resolution: {integrity: sha512-hTk71OPKnioN349sdj2DAoY+69eSerB3MN4Zwz6mosr1QFzIMkfkNOtBeC+Gm0yi0V0EY5LeBYFgqb3oXbtTbw==} + + '@thi.ng/hex@1.0.4': + resolution: {integrity: sha512-9ofIG4nXhEskGeOJthpi/9LXFIPrlZ/MmHpgLWa3wNqTVhODP/o++mu9jDKojHEpKvswkkFCE+mSVmMu8xo4mQ==} + + '@thi.ng/random@2.4.8': + resolution: {integrity: sha512-4JJB8zbaPxjlAp1kCqsBbs6eN4Ivd/5fs1e4GlvmNkyGSucHIDTWvw6NnQWqUx2oPaAEDB9CFCH7SOcGC/cwkw==} + + '@thi.ng/zipper@1.0.3': + resolution: {integrity: sha512-dWfuk5nzf5wGEmcF90AXNEuWr3NVwRF+cf/9ZSE6xImA7Vy5XpHNMwLHFszZaC+kqiDXr+EZ0lXWDF46a8lSPA==} + + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/auth-header@1.0.6': + resolution: {integrity: sha512-TjQyS7b+msxND/uuvza7FWSiBBLtI5y9vB55rpTeMcO2M5DSs4ony9WNKDvZLJL2w5aJH2A4C+ht1c9MPHhJWQ==} + + '@types/aws4@1.11.6': + resolution: {integrity: sha512-5CnVUkHNyLGpD9AnOcK66YyP0qvIh6nhJJoeK8zSl5YKikUcUbdB7SlHevUYVqicgeh6j5AJa1qa/h08dSZHoA==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.5': + resolution: {integrity: sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==} + + '@types/better-sqlite3@7.6.10': + resolution: {integrity: sha512-TZBjD+yOsyrUJGmcUj6OS3JADk3+UZcNv3NOBqGkM09bZdi28fNZw8ODqbMOLfKCu7RYCO62/ldq1iHbzxqoPw==} + + '@types/breejs__later@4.1.5': + resolution: {integrity: sha512-O7VIO7sktsIwmLUyEeUnLMJ+QD2pv0yBGI2EMbVmwC1GOOTWJAaneL82ZyIwRgpEjJ9ciUHP8LuuuU55uj5ZjA==} + + '@types/bunyan@1.8.11': + resolution: {integrity: sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ==} + + '@types/bunyan@1.8.9': + resolution: {integrity: sha512-ZqS9JGpBxVOvsawzmVt30sP++gSQMTejCkIAQ3VdadOcRE8izTyW66hufvwLeH+YEGP6Js2AW7Gz+RMyvrEbmw==} + + '@types/cacache@17.0.2': + resolution: {integrity: sha512-IrqHzVX2VRMDQQKa7CtKRnuoCLdRJiLW6hWU+w7i7+AaQ0Ii5bKwJxd5uRK4zBCyrHd3tG6G8zOm2LplxbSfQg==} + + '@types/cacheable-request@6.0.3': + resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} + + '@types/callsite@1.0.34': + resolution: {integrity: sha512-eglitkkbqiQiijtKsUvOcQm+E6qLMPcggjDJXeqNBnLxdzffRGop2+2QDN/8pHh396/jN5cmIwweNKUqKJ50mQ==} + + '@types/changelog-filename-regex@2.0.2': + resolution: {integrity: sha512-H9iuCn3Ata8075f1Nyg/WScyicJ3eXr7AklsOrPeME3sa8izDlpBhbWurtdZJfuo4Vc5+J7wNoD9Yo1d66sj+A==} + + '@types/clean-git-ref@2.0.2': + resolution: {integrity: sha512-2z9rK9ayJHatZt9oDLCGE0FsArvjG1xWGuSufh6FTbbPdbpGj7cpzhfcKbVnyrwatTQ5KyxhurmGBM2xDa8Jgw==} + + '@types/common-tags@1.8.4': + resolution: {integrity: sha512-S+1hLDJPjWNDhcGxsxEbepzaxWqURP/o+3cP4aa2w7yBXgdcmKGQtZzP8JbyfOd0m+33nh+8+kvxYE2UJtBDkg==} + + '@types/conventional-commits-detector@1.0.2': + resolution: {integrity: sha512-Yzo8dW+b2vziyDD9WNY+IPq4rcZyguHNuyNZC3wv0igpVFRd7VWHufl+vRQaCzDR2ftPTB1VPwbvXxWVpzBo+g==} + + '@types/diff@5.2.1': + resolution: {integrity: sha512-uxpcuwWJGhe2AR1g8hD9F5OYGCqjqWnBUQFD8gMZsDbv8oPHzxJF6iMO6n8Tk0AdzlxoaaoQhOYlIg/PukVU8g==} + + '@types/emscripten@1.39.11': + resolution: {integrity: sha512-dOeX2BeNA7j6BTEqJQL3ut0bRCfsyQMd5i4FT8JfHfYhAOuJPCGh0dQFbxVJxUyQ+75x6enhDdndGb624/QszA==} + + '@types/eslint@8.56.10': + resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==} + + '@types/estree@1.0.5': + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + + '@types/fs-extra@11.0.4': + resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} + + '@types/git-url-parse@9.0.3': + resolution: {integrity: sha512-Wrb8zeghhpKbYuqAOg203g+9YSNlrZWNZYvwxJuDF4dTmerijqpnGbI79yCuPtHSXHPEwv1pAFUB4zsSqn82Og==} + + '@types/github-url-from-git@1.5.3': + resolution: {integrity: sha512-0vnjtdEpqLTRBlgkzXsRaAQ0T8Nx48fW7qWl/Y5a4MTXEL2mXFV8rNPiFPCYrJFPOeyUJRzNzcs91MgJd+fFSA==} + + '@types/global-agent@2.1.3': + resolution: {integrity: sha512-rGtZZcgZcKWuKNTkGBGsqyOQ7Nn2MjXh4+xeZbf+5b5KMUx8H1rTqLRackxos7pUlreszbYjQcop5JvqCnZlLw==} + + '@types/graceful-fs@4.1.9': + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + + '@types/http-cache-semantics@4.0.4': + resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} + + '@types/ini@4.1.0': + resolution: {integrity: sha512-mTehMtc+xtnWBBvqizcqYCktKDBH2WChvx1GU3Sfe4PysFDXiNe+1YwtpVX1MDtCa4NQrSPw2+3HmvXHY3gt1w==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/js-yaml@4.0.9': + resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + + '@types/json-dup-key-validator@1.0.2': + resolution: {integrity: sha512-zJSAGITlz2nFT7xcKsvns8UifwSJpKuhgsdZj7+WoxiixiGnIefNiLK2uNhEICRkI9S2ccU6RYdqPS7iJRtU7Q==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + + '@types/jsonfile@6.1.4': + resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} + + '@types/keyv@3.1.4': + resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} + + '@types/linkify-it@3.0.5': + resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==} + + '@types/linkify-markdown@1.0.3': + resolution: {integrity: sha512-BnuGqDmpzmXCDMXHzgle/vMRUnbFcWclts0+n7Or421exav3XG6efl9gsxamLET6QPhX+pMnxcsHgnAO/daj9w==} + + '@types/lodash@4.17.1': + resolution: {integrity: sha512-X+2qazGS3jxLAIz5JDXDzglAF3KpijdhFxlf/V1+hEsOUc+HnWi81L/uv/EvGuV90WY+7mPGFCUDGfQC3Gj95Q==} + + '@types/luxon@3.4.2': + resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==} + + '@types/markdown-it@14.0.1': + resolution: {integrity: sha512-6WfOG3jXR78DW8L5cTYCVVGAsIFZskRHCDo5tbqa+qtKVt4oDRVH7hyIWu1SpDQJlmIoEivNQZ5h+AGAOrgOtQ==} + + '@types/markdown-table@2.0.0': + resolution: {integrity: sha512-fVZN/DRjZvjuk+lo7ovlI/ZycS51gpYU5vw5EcFeqkcX6lucQ+UWgEOH2O4KJHkSck4DHAY7D7CkVLD0wzc5qw==} + + '@types/marshal@0.5.3': + resolution: {integrity: sha512-ptxKIirn/lt95Zi/MErrtn/K8VvrByNOAF9gxbIJCxWj9CXAifjAvm/bRMg7WXQjwi1DlbXG6HJ1RzHe6oYEug==} + + '@types/mdast@3.0.15': + resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} + + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + + '@types/minimist@1.2.5': + resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + + '@types/moo@0.5.5': + resolution: {integrity: sha512-eXQpwnkI4Ntw5uJg6i2PINdRFWLr55dqjuYQaLHNjvqTzF14QdNWbCbml9sza0byyXNA0hZlHtcdN+VNDcgVHA==} + + '@types/moo@0.5.9': + resolution: {integrity: sha512-ZsFVecFi66jGQ6L41TonEaBhsIVeVftTz6iQKWTctzacHhzYHWvv9S0IyAJi4BhN7vb9qCQ3+kpStP2vbZqmDg==} + + '@types/ms@0.7.34': + resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} + + '@types/node@18.19.33': + resolution: {integrity: sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A==} + + '@types/normalize-package-data@2.4.4': + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + + '@types/parse-link-header@2.0.3': + resolution: {integrity: sha512-ffLAxD6Xqcf2gSbtEJehj8yJ5R/2OZqD4liodQvQQ+hhO4kg1mk9ToEZQPMtNTm/zIQj2GNleQbsjPp9+UQm4Q==} + + '@types/responselike@1.0.3': + resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} + + '@types/semver-stable@3.0.2': + resolution: {integrity: sha512-uNLK57+EY0r8VprVwHytHhlTb1tUVZiWgXkMBKoeu1/3LaFq+ZiaG29xAC3APAWG7xdedwGqeUY8N1y9YG1vjw==} + + '@types/semver-utils@1.1.3': + resolution: {integrity: sha512-T+YwkslhsM+CeuhYUxyAjWm7mJ5am/K10UX40RuA6k6Lc7eGtq8iY2xOzy7Vq0GOqhl/xZl5l2FwURZMTPTUww==} + + '@types/semver@7.5.8': + resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + + '@types/shimmer@1.0.5': + resolution: {integrity: sha512-9Hp0ObzwwO57DpLFF0InUjUm/II8GmKAvzbefxQTihCb7KI6yc9yzf0nLc4mVdby5N4DRCgQM2wCup9KTieeww==} + + '@types/sinon@10.0.20': + resolution: {integrity: sha512-2APKKruFNCAZgx3daAyACGzWuJ028VVCUDk6o2rw/Z4PXT0ogwdV4KUegW0MwVs0Zu59auPXbbuBJHF12Sx1Eg==} + + '@types/sinonjs__fake-timers@8.1.5': + resolution: {integrity: sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/tar@6.1.13': + resolution: {integrity: sha512-IznnlmU5f4WcGTh2ltRu/Ijpmk8wiWXfF0VA4s+HPjHZgvFggk1YaIkbo5krX/zUCzWF8N/l4+W/LNxnvAJ8nw==} + + '@types/tmp@0.2.6': + resolution: {integrity: sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==} + + '@types/traverse@0.6.36': + resolution: {integrity: sha512-0ko4fZgKQf2J/2FvDJHua9KZ19zJkfI5FxG1ZeEUhezEwuq6UL+4T0IxcBfmHilBeAj7OSUTwrm/lPnh8EB1/Q==} + + '@types/treeify@1.0.3': + resolution: {integrity: sha512-hx0o7zWEUU4R2Amn+pjCBQQt23Khy/Dk56gQU5xi5jtPL1h83ACJCeFaB2M/+WO1AntvWrSoVnnCAfI1AQH4Cg==} + + '@types/unist@2.0.10': + resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} + + '@types/url-join@4.0.3': + resolution: {integrity: sha512-3l1qMm3wqO0iyC5gkADzT95UVW7C/XXcdvUcShOideKF0ddgVRErEQQJXBd2kvQm+aSgqhBGHGB38TgMeT57Ww==} + + '@types/validate-npm-package-name@4.0.2': + resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==} + + '@types/xmldoc@1.1.9': + resolution: {integrity: sha512-HLwIAudQ9xedPOK9rKd7gSHYzM5qtWOzae9z5tM7dRDR1hWeNlFSejfnxFMIv06mm2LmtX+pzVQ4GN86vf/b3g==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.32': + resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==} + + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + + '@typescript-eslint/eslint-plugin@7.10.0': + resolution: {integrity: sha512-PzCr+a/KAef5ZawX7nbyNwBDtM1HdLIT53aSA2DDlxmxMngZ43O8SIePOeX8H5S+FHXeI6t97mTt/dDdzY4Fyw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/experimental-utils@5.62.0': + resolution: {integrity: sha512-RTXpeB3eMkpoclG3ZHft6vG/Z30azNHuqY6wKPBHlVMZFuEvrtlEDe8gMqDb+SO+9hjC/pLekeSCryf9vMZlCw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@typescript-eslint/parser@7.10.0': + resolution: {integrity: sha512-2EjZMA0LUW5V5tGQiaa2Gys+nKdfrn2xiTIBLR4fxmPmVSvgPcKNW+AE/ln9k0A4zDUti0J/GZXMDupQoI+e1w==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@5.62.0': + resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/scope-manager@7.10.0': + resolution: {integrity: sha512-7L01/K8W/VGl7noe2mgH0K7BE29Sq6KAbVmxurj8GGaPDZXPr8EEQ2seOeAS+mEV9DnzxBQB6ax6qQQ5C6P4xg==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/type-utils@7.10.0': + resolution: {integrity: sha512-D7tS4WDkJWrVkuzgm90qYw9RdgBcrWmbbRkrLA4d7Pg3w0ttVGDsvYGV19SH8gPR5L7OtcN5J1hTtyenO9xE9g==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@5.62.0': + resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/types@7.10.0': + resolution: {integrity: sha512-7fNj+Ya35aNyhuqrA1E/VayQX9Elwr8NKZ4WueClR3KwJ7Xx9jcCdOrLW04h51de/+gNbyFMs+IDxh5xIwfbNg==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/typescript-estree@5.62.0': + resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/typescript-estree@7.10.0': + resolution: {integrity: sha512-LXFnQJjL9XIcxeVfqmNj60YhatpRLt6UhdlFwAkjNc6jSUlK8zQOl1oktAP8PlWFzPQC1jny/8Bai3/HPuvN5g==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@5.62.0': + resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@typescript-eslint/utils@7.10.0': + resolution: {integrity: sha512-olzif1Fuo8R8m/qKkzJqT7qwy16CzPRWBvERS0uvyc+DHd8AKbO4Jb7kpAvVzMmZm8TrHnI7hvjN4I05zow+tg==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + + '@typescript-eslint/visitor-keys@5.62.0': + resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/visitor-keys@7.10.0': + resolution: {integrity: sha512-9ntIVgsi6gg6FIq9xjEO4VQJvwOqA3jaBFQJ/6TK5AvEup2+cECI6Fh7QiBxmfMHXU0V0J4RyPeOU1VDNzl9cg==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@ungap/structured-clone@1.2.0': + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + + '@yarnpkg/core@4.0.5': + resolution: {integrity: sha512-6ib9l8P30GxHvxZo3170hr5PCy2qQnI4N4lXwNJxJnV0i46UlgLA4hlGp/kFVttteATGeckfduIDyWZgjn9sPw==} + engines: {node: '>=18.12.0'} + + '@yarnpkg/fslib@3.0.2': + resolution: {integrity: sha512-dqSpZCj9ZeqMZzITHvpi5qKS9MCLWZYCl6NHCSl3a+BqiyLApehWAy60zjv9F1fxKZ6RKDgHLhBWhx8RwPm6Dw==} + engines: {node: '>=18.12.0'} + + '@yarnpkg/libzip@3.0.1': + resolution: {integrity: sha512-fiqRLk2fyd2r34/Qc7HlP8fKIo1CK5CZpLNObJwnbFmZQN2hVanovFlG++3oH3qYJymEmjPl5EGsygcEJZl4Pg==} + engines: {node: '>=18.12.0'} + peerDependencies: + '@yarnpkg/fslib': ^3.0.2 + + '@yarnpkg/parsers@3.0.2': + resolution: {integrity: sha512-/HcYgtUSiJiot/XWGLOlGxPYUG65+/31V8oqk17vZLW1xlCoR4PampyePljOxY2n8/3jz9+tIFzICsyGujJZoA==} + engines: {node: '>=18.12.0'} + + '@yarnpkg/shell@4.0.2': + resolution: {integrity: sha512-DLZSx06OoEbPY1uePt7pKEgpWDk96PldrCdWBPqI5Np5/YAEo6+toVcjz+6fORMOE8PS3Bsep1Nfm2mUrY1Oxg==} + engines: {node: '>=18.12.0'} + hasBin: true + + JSONStream@1.3.5: + resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} + hasBin: true + + abbrev@2.0.0: + resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.2: + resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} + engines: {node: '>=0.4.0'} + + acorn@8.11.3: + resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} + engines: {node: '>=0.4.0'} + hasBin: true + + adm-zip@0.5.12: + resolution: {integrity: sha512-6TVU49mK6KZb4qG6xWaaM4C7sA/sgUMLy/JYMOzkcp3BvVLpW0fXDFQiIzAuxFCt/2+xD7fNIiPFAoLZPhVNLQ==} + engines: {node: '>=6.0'} + + agent-base@7.1.1: + resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} + engines: {node: '>= 14'} + + agentkeepalive@4.5.0: + resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} + engines: {node: '>= 8.0.0'} + + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + + aggregate-error@5.0.0: + resolution: {integrity: sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==} + engines: {node: '>=18'} + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-escapes@6.2.1: + resolution: {integrity: sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==} + engines: {node: '>=14.16'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + ansicolors@0.3.2: + resolution: {integrity: sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + append-transform@2.0.0: + resolution: {integrity: sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==} + engines: {node: '>=8'} + + archy@1.0.0: + resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + argv-formatter@1.0.0: + resolution: {integrity: sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==} + + array-buffer-byte-length@1.0.1: + resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + engines: {node: '>= 0.4'} + + array-ify@1.0.0: + resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} + + array-includes@3.1.8: + resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} + engines: {node: '>= 0.4'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + array.prototype.findlastindex@1.2.5: + resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.2: + resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.2: + resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.3: + resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} + engines: {node: '>= 0.4'} + + arrify@1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + + asn1.js@5.4.1: + resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} + + auth-header@1.0.0: + resolution: {integrity: sha512-CPPazq09YVDUNNVWo4oSPTQmtwIzHusZhQmahCKvIsk0/xH6U3QsMAv3sM+7+Q0B1K2KJ/Q38OND317uXs4NHA==} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + aws-sdk-client-mock@4.0.0: + resolution: {integrity: sha512-/rxo+pzCFaUozK7TyCqo3GYwzdBGn9Ai6EsT8ytXDoUXlD/Q5hw9hj2lOkCAyubECzGJFHMmQg9GZ1GOGlN/qQ==} + + aws4@1.12.0: + resolution: {integrity: sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==} + + azure-devops-node-api@13.0.0: + resolution: {integrity: sha512-T/i3pt2Dxb2//1+TJT05Ff5heUmQEWKwa8sdguIhdRYT3Zge9FYw98zpfFvCD7CZsz6AN74SKGgqF3ISVN2TGg==} + + babel-jest@29.7.0: + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + + babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + + babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + babel-preset-current-node-syntax@1.0.1: + resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} + peerDependencies: + '@babel/core': ^7.0.0 + + babel-preset-jest@29.6.3: + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + + backslash@0.2.0: + resolution: {integrity: sha512-Avs+8FUZ1HF/VFP4YWwHQZSGzRPm37ukU1JQYQWijuHhtXdOuAzcZ8PcAzfIw898a8PyBzdn+RtnKA6MzW0X2A==} + + bail@1.0.5: + resolution: {integrity: sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + before-after-hook@2.2.3: + resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} + + better-sqlite3@9.6.0: + resolution: {integrity: sha512-yR5HATnqeYNVnkaUTf4bOP2dJSnyhP4puJN/QPRyx4YkBEEUxib422n2XzPqDEHjQQqazoYoADdAm5vE15+dAQ==} + + bignumber.js@9.1.2: + resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + bn.js@4.12.0: + resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} + + bn@1.0.5: + resolution: {integrity: sha512-7TvGbqbZb6lDzsBtNz1VkdXXV0BVmZKPPViPmo2IpvwaryF7P+QKYKACyVkwo2mZPr2CpFiz7EtgPEcc3o/JFQ==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + boolean@3.2.0: + resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + + bottleneck@2.19.5: + resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} + + bowser@2.11.0: + resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + + browserslist@4.23.0: + resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + bunyan@1.8.15: + resolution: {integrity: sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==} + engines: {'0': node >=0.10.0} + hasBin: true + + bzip-deflate@1.0.0: + resolution: {integrity: sha512-9RMnpiJqMYMJcLdr4pxwowZ8Zh3P+tVswE/bnX6tZ14UGKNcdV5WVK2P+lGp2As+RCjl+i3SFJ117HyCaaHNDA==} + + cacache@18.0.3: + resolution: {integrity: sha512-qXCd4rh6I07cnDqh8V48/94Tc/WSfj+o3Gn6NZ0aZovS255bUx8O13uKxRFd2eWG0xgsco7+YItQNPaa5E85hg==} + engines: {node: ^16.14.0 || >=18.0.0} + + cacheable-lookup@5.0.4: + resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} + engines: {node: '>=10.6.0'} + + cacheable-request@7.0.4: + resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} + engines: {node: '>=8'} + + caching-transform@4.0.0: + resolution: {integrity: sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==} + engines: {node: '>=8'} + + call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} + + callsite@1.0.0: + resolution: {integrity: sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase-keys@6.2.2: + resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} + engines: {node: '>=8'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001616: + resolution: {integrity: sha512-RHVYKov7IcdNjVHJFNY/78RdG4oGVjbayxv8u5IO74Wv7Hlq4PnJE6mo/OjFijjVFNy5ijnCt6H3IIo4t+wfEw==} + + cardinal@2.1.1: + resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} + hasBin: true + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@3.0.0: + resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} + engines: {node: '>=8'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + changelog-filename-regex@2.0.1: + resolution: {integrity: sha512-DZdyJpCprw8V3jp8V2x13nAA05Yy/IN+Prowj+0mrAHNENYkuMtNI4u5m449TTjPqShIslQSEuXee+Jtkn4m+g==} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + character-entities-legacy@1.1.4: + resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} + + character-entities@1.2.4: + resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==} + + character-reference-invalid@1.1.4: + resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + cjs-module-lexer@1.3.1: + resolution: {integrity: sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==} + + clean-git-ref@2.0.1: + resolution: {integrity: sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw==} + + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + + clean-stack@5.2.0: + resolution: {integrity: sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==} + engines: {node: '>=14.16'} + + cli-table3@0.6.4: + resolution: {integrity: sha512-Lm3L0p+/npIQWNIiyF/nAn7T5dnOwR3xNTHXYEBFBFVPXzCVNZ5lqEC/1eo/EVfpDsQ1I+TX4ORPQgp+UI0CRw==} + engines: {node: 10.* || >= 12.*} + + clipanion@4.0.0-rc.3: + resolution: {integrity: sha512-+rJOJMt2N6Oikgtfqmo/Duvme7uz3SIedL2b6ycgCztQMiTfr3aQh2DDyLHl+QUPClKMNpSg3gDJFvNQYIcq1g==} + peerDependencies: + typanion: '*' + + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone-response@1.0.3: + resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} + + cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + collect-v8-coverage@1.0.2: + resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + + commander@12.0.0: + resolution: {integrity: sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==} + engines: {node: '>=18'} + + common-tags@1.8.2: + resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} + engines: {node: '>=4.0.0'} + + commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + + compare-func@2.0.0: + resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + + conventional-changelog-angular@7.0.0: + resolution: {integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==} + engines: {node: '>=16'} + + conventional-changelog-conventionalcommits@7.0.2: + resolution: {integrity: sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==} + engines: {node: '>=16'} + + conventional-changelog-writer@7.0.1: + resolution: {integrity: sha512-Uo+R9neH3r/foIvQ0MKcsXkX642hdm9odUp7TqgFS7BsalTcjzRlIfWZrZR1gbxOozKucaKt5KAbjW8J8xRSmA==} + engines: {node: '>=16'} + hasBin: true + + conventional-commits-detector@1.0.3: + resolution: {integrity: sha512-VlBCTEg34Bbvyh7MPYtmgoYPsP69Z1BusmthbiUbzTiwfhLZWRDEWsJHqWyiekSC9vFCHGT/jKOzs8r21MUZ5g==} + engines: {node: '>=6.9.0'} + hasBin: true + + conventional-commits-filter@4.0.0: + resolution: {integrity: sha512-rnpnibcSOdFcdclpFwWa+pPlZJhXE7l+XK04zxhbWrhgpR96h33QLz8hITTXbcYICxVr3HZFtbtUAQ+4LdBo9A==} + engines: {node: '>=16'} + + conventional-commits-parser@5.0.0: + resolution: {integrity: sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==} + engines: {node: '>=16'} + hasBin: true + + convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + core-js-pure@3.37.0: + resolution: {integrity: sha512-d3BrpyFr5eD4KcbRvQ3FTUx/KWmaDesr7+a3+1+P46IUnNoEt+oiLijPINZMEon7w9oGkIINWxrBAU9DEciwFQ==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + create-jest@29.7.0: + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cron-parser@4.9.0: + resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} + engines: {node: '>=12.0.0'} + + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + + crypto-random-string@4.0.0: + resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} + engines: {node: '>=12'} + + css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + + css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + + dargs@7.0.0: + resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} + engines: {node: '>=8'} + + data-view-buffer@1.0.1: + resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.1: + resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.0: + resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} + engines: {node: '>= 0.4'} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize-keys@1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} + + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + dedent@1.5.3: + resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-equal@2.2.3: + resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} + engines: {node: '>= 0.4'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + default-require-extensions@3.0.1: + resolution: {integrity: sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==} + engines: {node: '>=8'} + + defer-to-connect@2.0.1: + resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} + engines: {node: '>=10'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + deprecation@2.3.1: + resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + + detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + + detect-node@2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + + diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} + engines: {node: '>=0.3.1'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.1.0: + resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} + + dot-prop@5.3.0: + resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} + engines: {node: '>=8'} + + dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + + dtrace-provider@0.8.8: + resolution: {integrity: sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==} + engines: {node: '>=0.10'} + + duplexer2@0.1.4: + resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + + editorconfig@2.0.0: + resolution: {integrity: sha512-s1NQ63WQ7RNXH6Efb2cwuyRlfpbtdZubvfNe4vCuoyGPewNPY7vah8JUSOFBiJ+jr99Qh8t0xKv0oITc1dclgw==} + engines: {node: '>=16'} + hasBin: true + + electron-to-chromium@1.4.756: + resolution: {integrity: sha512-RJKZ9+vEBMeiPAvKNWyZjuYyUqMndcP1f335oHqn3BEQbs2NFtVrnK5+6Xg5wSM9TknNNpWghGDUCKGYF+xWXw==} + + email-addresses@5.0.0: + resolution: {integrity: sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==} + + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + + emoji-regex@10.3.0: + resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + emojibase-data@15.3.0: + resolution: {integrity: sha512-cYsGq57W8wOd1CqQCjmtP1DpmlTVvJY+Um5UobWUQuTnqb8cO2cuqrxJxSIqxLcYZ3rtialT5kivoWigszdslg==} + peerDependencies: + emojibase: '*' + + emojibase-regex@15.3.0: + resolution: {integrity: sha512-EBz/292VBF9naBPBsGzkZUccgIv1xJibTXIINl8SezgVRnTCpKJx7MgZcR+UAd2RwjGkRJJZ/lhP7riOFZLicA==} + + emojibase@15.3.0: + resolution: {integrity: sha512-lFdQb14hoznwDh1xDoOFzkPdeeexmbuJdhUUjDaJZf/+jK+6Z3HkI5hWOemWfEdaMxtTujc1vNRx9DKtdtiOXA==} + + emojilib@2.4.0: + resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} + + encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + + end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + + enhanced-resolve@5.16.0: + resolution: {integrity: sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==} + engines: {node: '>=10.13.0'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + env-ci@10.0.0: + resolution: {integrity: sha512-U4xcd/utDYFgMh0yWj07R1H6L5fwhVbmxBCpnL0DbVSDZVnsC82HONw0wxtxNkIAcua3KtbomQvIk5xFZGAQJw==} + engines: {node: ^18.17 || >=20.6.1} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + es-abstract@1.23.3: + resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-get-iterator@1.1.3: + resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} + + es-object-atoms@1.0.0: + resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.0.3: + resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.0.2: + resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} + + es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + + es6-error@4.1.1: + resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + + escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + engines: {node: '>=6'} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + eslint-formatter-gha@1.5.0: + resolution: {integrity: sha512-mpZ8g/rfxn/igeAapQpbUrnvaP3zPpru3Y2I1yfDdRazzcNvYJmrAb9NLbNce0TQYbLBmg38X/drUOv0QhLyaw==} + + eslint-formatter-json@8.40.0: + resolution: {integrity: sha512-0bXo4At1EoEU23gFfN7wcDeqRXDHLJnvDOuQKD3Q6FkBlk7L2oVNPYg/sciIWdYrUnCBcKuMit3IWXkdSfzChg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-formatter-stylish@8.40.0: + resolution: {integrity: sha512-blbD5ZSQnjNEUaG38VCO4WG9nfDQWE8/IOmt8DFRHXUIfZikaIXmsQTdWNFk0/e0j7RgIVRza86MpsJ+aHgFLg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-import-resolver-typescript@3.6.1: + resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + + eslint-module-utils@2.8.1: + resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-import@2.29.1: + resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-jest-formatting@3.1.0: + resolution: {integrity: sha512-XyysraZ1JSgGbLSDxjj5HzKKh0glgWf+7CkqxbTqb7zEhW7X2WHo5SBQ8cGhnszKN+2Lj3/oevBlHNbHezoc/A==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=0.8.0' + + eslint-plugin-jest@27.9.0: + resolution: {integrity: sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==} + 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 + + eslint-plugin-promise@6.1.1: + resolution: {integrity: sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + + eslint-plugin-typescript-enum@2.1.0: + resolution: {integrity: sha512-n6RO89KJ2V2nHVAdIq1q3IBeYZSNZjBreqXOzpjmsBtw+NNhSTTSQXqwO00VYOce9Gy8cr2cDEYpj0Km+Ij90Q==} + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.0: + resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + + exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + expect-more-jest@5.5.0: + resolution: {integrity: sha512-l3SwYCvT02r97uFlJnFQQiFGFEAdt6zHBiFDUiOuAfPxM1/kVGpzNXw9UM66WieNsK/e/G+UzuW39GGxqGjG8A==} + + expect-more@1.3.0: + resolution: {integrity: sha512-HnXT5nJb9V3DMnr5RgA1TiKbu5kRaJ0GD1JkuhZvnr1Qe3HJq+ESnrcl/jmVUZ8Ycnl3Sp0OTYUhmO36d2+zow==} + + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + exponential-backoff@3.1.1: + resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-xml-parser@4.2.5: + resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==} + hasBin: true + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + + figures@2.0.0: + resolution: {integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==} + engines: {node: '>=4'} + + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + + find-cache-dir@3.3.2: + resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} + engines: {node: '>=8'} + + find-packages@10.0.4: + resolution: {integrity: sha512-JmO9lEBUEYOiRw/bdbdgFWpGFgBZBGLcK/5GjQKo3ZN+zR6jmQOh9gWyZoqxlQmnldZ9WBWhna0QYyuq6BxvRg==} + engines: {node: '>=14.6'} + + find-up-simple@1.0.0: + resolution: {integrity: sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==} + engines: {node: '>=18'} + + find-up@2.1.0: + resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} + engines: {node: '>=4'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + find-versions@5.1.0: + resolution: {integrity: sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==} + engines: {node: '>=12'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + + for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + + foreground-child@2.0.0: + resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==} + engines: {node: '>=8.0.0'} + + foreground-child@3.1.1: + resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + engines: {node: '>=14'} + + from2@2.3.0: + resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==} + + fromentries@1.3.2: + resolution: {integrity: sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fs-extra@11.2.0: + resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} + engines: {node: '>=14.14'} + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + + fs-minipass@3.0.3: + resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.6: + resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + gaxios@6.5.0: + resolution: {integrity: sha512-R9QGdv8j4/dlNoQbX3hSaK/S0rkMijqjVvW3YM06CoBdbU/VdKd159j4hePpng0KuE6Lh6JJ7UdmVGJZFcAG1w==} + engines: {node: '>=14'} + + gcp-metadata@6.1.0: + resolution: {integrity: sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==} + engines: {node: '>=14'} + + generic-pool@3.9.0: + resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} + engines: {node: '>= 4'} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} + + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-stream@7.0.1: + resolution: {integrity: sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==} + engines: {node: '>=16'} + + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + + get-symbol-description@1.0.2: + resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.7.4: + resolution: {integrity: sha512-ofbkKj+0pjXjhejr007J/fLf+sW+8H7K5GCm+msC8q3IpvgjobpyPqSRFemNyIMxklC0zeJpi7VDFna19FacvQ==} + + git-log-parser@1.2.0: + resolution: {integrity: sha512-rnCVNfkTL8tdNryFuaY0fYiBWEBcgF748O6ZI61rslBvr2o7U65c2/6npCRqH40vuAhtgtDiqLTJjBVdrejCzA==} + + git-raw-commits@2.0.11: + resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==} + engines: {node: '>=10'} + hasBin: true + + git-up@7.0.0: + resolution: {integrity: sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==} + + git-url-parse@14.0.0: + resolution: {integrity: sha512-NnLweV+2A4nCvn4U/m2AoYu0pPKlsmhK9cknG7IMwsjFY1S2jxM+mAhsDxyxfCIGfGaD+dozsyX4b6vkYc83yQ==} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + + github-url-from-git@1.5.0: + resolution: {integrity: sha512-WWOec4aRI7YAykQ9+BHmzjyNlkfJFG8QLXnDTsLz/kZefq7qkzdfo4p6fkYYMIq1aj+gZcQs/1HQhQh3DPPxlQ==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.3.15: + resolution: {integrity: sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==} + engines: {node: '>=16 || 14 >=14.18'} + hasBin: true + + glob@6.0.4: + resolution: {integrity: sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + + global-agent@3.0.0: + resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} + engines: {node: '>=10.0'} + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + globby@14.0.1: + resolution: {integrity: sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==} + engines: {node: '>=18'} + + good-enough-parser@1.1.23: + resolution: {integrity: sha512-QUcQZutczESpdo2w9BMG6VpLFoq9ix7ER5HLM1mAdZdri2F3eISkCb8ep84W6YOo0grYWJdyT/8JkYqGjQfSSQ==} + engines: {node: '>=18.12.0', yarn: ^1.17.0} + + google-auth-library@9.10.0: + resolution: {integrity: sha512-ol+oSa5NbcGdDqA+gZ3G3mev59OHBZksBTxY/tYwjtcp1H/scAFwJfSQU9/1RALoyZ7FslNbke8j4i3ipwlyuQ==} + engines: {node: '>=14'} + + gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + + got@11.8.6: + resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} + engines: {node: '>=10.19.0'} + + graceful-fs@4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graph-data-structure@3.5.0: + resolution: {integrity: sha512-AAgjRtBZC1acIExgK2otv2LDdcYeZdQFKiEStXRDTyaVs6sUUaGUif05pCczTqAU4ny82NQtM1p5PK7AQEYgRA==} + + grapheme-splitter@1.0.4: + resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + graphql@16.8.1: + resolution: {integrity: sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + + gtoken@7.1.0: + resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==} + engines: {node: '>=14.0.0'} + + handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true + + hard-rejection@2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} + + has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + engines: {node: '>= 0.4'} + + has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasha@5.2.2: + resolution: {integrity: sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==} + engines: {node: '>=8'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + hook-std@3.0.0: + resolution: {integrity: sha512-jHRQzjSDzMtFy34AGj1DN+vq54WVuhSvKgrHf0OMiFQTwDD4L/qqofVEWjLOBMTn5+lCD3fPg32W9yOfnEJTTw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + hosted-git-info@2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + + hosted-git-info@4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} + + hosted-git-info@7.0.2: + resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} + engines: {node: ^16.14.0 || >=18.0.0} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-cache-semantics@4.1.1: + resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + http2-wrapper@1.0.3: + resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} + engines: {node: '>=10.19.0'} + + https-proxy-agent@7.0.4: + resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==} + engines: {node: '>= 14'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + + husky@9.0.11: + resolution: {integrity: sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==} + engines: {node: '>=18'} + hasBin: true + + hyperdyperid@1.2.0: + resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==} + engines: {node: '>=10.18'} + + iced-error@0.0.13: + resolution: {integrity: sha512-yEEaG8QfyyRL0SsbNNDw3rVgTyqwHFMCuV6jDvD43f/2shmdaFXkqvFLGhDlsYNSolzYHwVLM/CrXt9GygYopA==} + + iced-lock@1.1.0: + resolution: {integrity: sha512-J9UMVitgTMYrkUil5EB9/Q4BPWiMpFH156yjDlmMoMRKs3s3PnXj/6G0UlzIOGnNi5JVNk/zVYLXVnuo+1QnqQ==} + + iced-lock@2.0.1: + resolution: {integrity: sha512-J6dnGMpAoHNyACUYJYhiJkLY7YFRTa7NMZ8ZygpYB3HNDOGWtzv55+kT2u1zItRi4Y1EXruG9d1VDsx8R5faTw==} + + iced-runtime-3@3.0.5: + resolution: {integrity: sha512-OHU64z4Njq4EdoGyRId5NgUQKy6R1sr1wufc1fVxwpqKsM8yWagqmKCRlt//zKKIPOfZw7kQ1iN4m+/2s8WSeg==} + + iced-runtime@1.0.4: + resolution: {integrity: sha512-rgiJXNF6ZgF2Clh/TKUlBDW3q51YPDJUXmxGQXx1b8tbZpVpTn+1RX9q1sjNkujXIIaVxZByQzPHHORg7KV51g==} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.1: + resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + engines: {node: '>= 4'} + + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + import-from-esm@1.3.4: + resolution: {integrity: sha512-7EyUlPFC0HOlBDpUFGfYstsU7XHxZJKAAMzCT8wZ0hMW7b+hG51LIKTDcsgtz8Pu6YC0HqRVbX+rVUtsGMUKvg==} + engines: {node: '>=16.20'} + + import-in-the-middle@1.7.4: + resolution: {integrity: sha512-Lk+qzWmiQuRPPulGQeK5qq0v32k2bHnWrRPFgqyvhw7Kkov5L6MOLOIU3pcWeujc9W4q54Cp3Q2WV16eQkc7Bg==} + + import-local@3.1.0: + resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} + engines: {node: '>=8'} + hasBin: true + + import-meta-resolve@4.1.0: + resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + indent-string@5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + + index-to-position@0.1.2: + resolution: {integrity: sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==} + engines: {node: '>=18'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + ini@4.1.2: + resolution: {integrity: sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + install-artifact-from-github@1.3.5: + resolution: {integrity: sha512-gZHC7f/cJgXz7MXlHFBxPVMsvIbev1OQN1uKQYKVJDydGNm9oYf9JstbU4Atnh/eSvk41WtEovoRm+8IF686xg==} + hasBin: true + + internal-slot@1.0.7: + resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} + engines: {node: '>= 0.4'} + + into-stream@7.0.0: + resolution: {integrity: sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==} + engines: {node: '>=12'} + + ip-address@9.0.5: + resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} + engines: {node: '>= 12'} + + is-alphabetical@1.0.4: + resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} + + is-alphanumerical@1.0.4: + resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==} + + is-arguments@1.1.1: + resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.4: + resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + + is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + + is-buffer@2.0.5: + resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} + engines: {node: '>=4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.13.1: + resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + + is-data-view@1.0.1: + resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} + engines: {node: '>= 0.4'} + + is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + + is-decimal@1.0.4: + resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + + is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hexadecimal@1.0.4: + resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} + + is-lambda@1.0.1: + resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-plain-obj@1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + + is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + + is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.3: + resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + engines: {node: '>= 0.4'} + + is-ssh@1.4.0: + resolution: {integrity: sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + + is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + + is-text-path@2.0.0: + resolution: {integrity: sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==} + engines: {node: '>=8'} + + is-typed-array@1.1.13: + resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} + engines: {node: '>= 0.4'} + + is-typedarray@1.0.0: + resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} + + is-unicode-supported@2.0.0: + resolution: {integrity: sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==} + engines: {node: '>=18'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + + is-weakset@2.0.3: + resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} + engines: {node: '>= 0.4'} + + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isexe@3.1.1: + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} + + issue-parser@6.0.0: + resolution: {integrity: sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA==} + engines: {node: '>=10.13'} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-hook@3.0.0: + resolution: {integrity: sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==} + engines: {node: '>=8'} + + istanbul-lib-instrument@4.0.3: + resolution: {integrity: sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==} + engines: {node: '>=8'} + + istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.2: + resolution: {integrity: sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==} + engines: {node: '>=10'} + + istanbul-lib-processinfo@2.0.3: + resolution: {integrity: sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + + jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} + + java-properties@1.0.2: + resolution: {integrity: sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==} + engines: {node: '>= 0.6.0'} + + jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-cli@29.7.0: + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@29.7.0: + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + 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 + + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-extended@4.0.2: + resolution: {integrity: sha512-FH7aaPgtGYHc9mRjriS0ZEHYM5/W69tLrFTIdzm+yJgeoCmmrSB/luSfMSqWP9O29QWHPEmJ4qmU6EwsZideog==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + jest: '>=27.2.5' + peerDependenciesMeta: + jest: + optional: true + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@29.4.1: + resolution: {integrity: sha512-k5h0u8V4nAEy6lSACepxL/rw78FLDkBnXhZVgFneVpnJONhb2DhZj/Gv4eNe+1XqQ5IhgUcqj745UwH0HJmMnA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-mock-extended@3.0.7: + resolution: {integrity: sha512-7lsKdLFcW9B9l5NzZ66S/yTQ9k8rFtnwYdCNuRU/81fqDWicNDVhitTSPnrGmNeNm0xyw0JHexEOShrIKRCIRQ==} + peerDependencies: + jest: ^24.0.0 || ^25.0.0 || ^26.0.0 || ^27.0.0 || ^28.0.0 || ^29.0.0 + typescript: ^3.0.0 || ^4.0.0 || ^5.0.0 + + jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest@29.7.0: + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsbn@1.1.0: + resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} + + jsesc@2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true + + json-bigint@1.0.0: + resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-dup-key-validator@1.0.3: + resolution: {integrity: sha512-JvJcV01JSiO7LRz7DY1Fpzn4wX2rJ3dfNTiAfnlvLNdhhnm0Pgdvhi2SGpENrZn7eSg26Ps3TPhOcuD/a4STXQ==} + + json-parse-better-errors@1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-parse-even-better-errors@3.0.2: + resolution: {integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json-stringify-pretty-compact@3.0.0: + resolution: {integrity: sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA==} + + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonata@2.0.5: + resolution: {integrity: sha512-wEse9+QLIIU5IaCgtJCPsFi/H4F3qcikWzF4bAELZiRz08ohfx3Q6CjDRf4ZPF5P/92RI3KIHtb7u3jqPaHXdQ==} + engines: {node: '>= 8'} + + jsonc-parser@3.2.1: + resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} + + jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + + jsonparse@1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} + + just-extend@6.2.0: + resolution: {integrity: sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==} + + jwa@2.0.0: + resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==} + + jws@4.0.0: + resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} + + keybase-ecurve@1.0.1: + resolution: {integrity: sha512-2GlVxDsNF+52LtYjgFsjoKuN7MQQgiVeR4HRdJxLuN8fm4mf4stGKPUjDJjky15c/98UsZseLjp7Ih5X0Sy1jQ==} + + keybase-nacl@1.1.4: + resolution: {integrity: sha512-7TFyWLq42CQs7JES9arR+Vnv/eMk5D6JT1Y8samrEA5ff3FOmaiRcXIVrwJQd3KJduxmSjgAjdkXlQK7Q437xQ==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + klona@2.0.6: + resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} + engines: {node: '>= 8'} + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lie@3.1.1: + resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + + load-json-file@4.0.0: + resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} + engines: {node: '>=4'} + + localforage@1.10.0: + resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==} + + locate-path@2.0.0: + resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} + engines: {node: '>=4'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + + lodash.capitalize@4.2.1: + resolution: {integrity: sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==} + + lodash.escaperegexp@4.1.2: + resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} + + lodash.flattendeep@4.4.0: + resolution: {integrity: sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==} + + lodash.get@4.4.2: + resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.uniqby@4.7.0: + resolution: {integrity: sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + longest-streak@2.0.4: + resolution: {integrity: sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==} + + lowercase-keys@2.0.0: + resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} + engines: {node: '>=8'} + + lru-cache@10.2.2: + resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} + engines: {node: 14 || >=16.14} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + luxon@3.4.4: + resolution: {integrity: sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==} + engines: {node: '>=12'} + + make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + make-fetch-happen@13.0.1: + resolution: {integrity: sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==} + engines: {node: ^16.14.0 || >=18.0.0} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + + map-obj@1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} + + map-obj@4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} + + markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + hasBin: true + + markdown-table@2.0.0: + resolution: {integrity: sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==} + + markdownlint-cli2-formatter-default@0.0.4: + resolution: {integrity: sha512-xm2rM0E+sWgjpPn1EesPXx5hIyrN2ddUnUwnbCsD/ONxYtw3PX6LydvdH6dciWAoFDpwzbHM1TO7uHfcMd6IYg==} + peerDependencies: + markdownlint-cli2: '>=0.0.4' + + markdownlint-cli2@0.13.0: + resolution: {integrity: sha512-Pg4nF7HlopU97ZXtrcVISWp3bdsuc5M0zXyLp2/sJv2zEMlInrau0ZKK482fQURzVezJzWBpNmu4u6vGAhij+g==} + engines: {node: '>=18'} + hasBin: true + + markdownlint-micromark@0.1.9: + resolution: {integrity: sha512-5hVs/DzAFa8XqYosbEAEg6ok6MF2smDj89ztn9pKkCtdKHVdPQuGMH7frFfYL9mLkvfFe4pTyAMffLbjf3/EyA==} + engines: {node: '>=18'} + + markdownlint@0.34.0: + resolution: {integrity: sha512-qwGyuyKwjkEMOJ10XN6OTKNOVYvOIi35RNvDLNxTof5s8UmyGHlCdpngRHoRGNvQVGuxO3BJ7uNSgdeX166WXw==} + engines: {node: '>=18'} + + marked-terminal@6.2.0: + resolution: {integrity: sha512-ubWhwcBFHnXsjYNsu+Wndpg0zhY4CahSpPlA70PlO0rR9r2sZpkyU+rkCsOWH+KMEkx847UpALON+HWgxowFtw==} + engines: {node: '>=16.0.0'} + peerDependencies: + marked: '>=1 <12' + + marked@9.1.6: + resolution: {integrity: sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==} + engines: {node: '>= 16'} + hasBin: true + + matcher@3.0.0: + resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} + engines: {node: '>=10'} + + mdast-util-find-and-replace@1.1.1: + resolution: {integrity: sha512-9cKl33Y21lyckGzpSmEQnIDjEfeeWelN5s1kUW1LwdB0Fkuq2u+4GdqcGEygYxJE8GVqCl0741bYXHgamfWAZA==} + + mdast-util-from-markdown@0.8.5: + resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==} + + mdast-util-to-markdown@0.6.5: + resolution: {integrity: sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==} + + mdast-util-to-string@1.1.0: + resolution: {integrity: sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==} + + mdast-util-to-string@2.0.0: + resolution: {integrity: sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==} + + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + + memfs@4.9.2: + resolution: {integrity: sha512-f16coDZlTG1jskq3mxarwB+fGRrd0uXWt+o1WIhRfOwbXQZqUDsTVxQBFK9JjRQHblg8eAG2JSbprDXKjc7ijQ==} + engines: {node: '>= 4.0.0'} + + memorystream@0.3.1: + resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} + engines: {node: '>= 0.10.0'} + + meow@12.1.1: + resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} + engines: {node: '>=16.10'} + + meow@7.1.1: + resolution: {integrity: sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA==} + engines: {node: '>=10'} + + meow@8.1.2: + resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} + engines: {node: '>=10'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromark@2.11.4: + resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==} + + micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + + mime@4.0.3: + resolution: {integrity: sha512-KgUb15Oorc0NEKPbvfa0wRU+PItIEZmiv+pyAO2i0oTIVTJhlzMclU7w4RXWQrSOVH5ax/p/CkIO7KI4OyFJTQ==} + engines: {node: '>=16'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + + mimic-response@1.0.1: + resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} + engines: {node: '>=4'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.2: + resolution: {integrity: sha512-PZOT9g5v2ojiTL7r1xF6plNHLtOeTpSlDI007As2NlA2aYBMfVom17yqa6QzhmDP8QOhn7LjHTg7DFCVSSa6yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimatch@9.0.4: + resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist-options@4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass-collect@2.0.1: + resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass-fetch@3.0.5: + resolution: {integrity: sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + minipass-flush@1.0.5: + resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} + engines: {node: '>= 8'} + + minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + + minipass-sized@1.0.3: + resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + engines: {node: '>=8'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@4.2.8: + resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + minipass@7.1.0: + resolution: {integrity: sha512-oGZRv2OT1lO2UF1zUcwdTb3wqUwI0kBGTgt/T7OdSj6M6N5m3o5uPf0AIW6lVxGGoiWUR7e2AwTE+xiwK8WQig==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + module-details-from-path@1.0.3: + resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} + + moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + + moo@0.5.2: + resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} + + more-entropy@0.0.7: + resolution: {integrity: sha512-e0TxQtU1F6/ZA8WnEA2JLQwwDqBTtZFLJSW7rWgUsQou35wx1IOL0g2O7q7oGoMgIJto+jHMnNGHLfSiylHRrw==} + + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mv@2.1.1: + resolution: {integrity: sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==} + engines: {node: '>=0.8.0'} + + nan@2.19.0: + resolution: {integrity: sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==} + + nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + napi-build-utils@1.0.2: + resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + ncp@2.0.0: + resolution: {integrity: sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==} + hasBin: true + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + nerf-dart@1.0.0: + resolution: {integrity: sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==} + + nise@5.1.9: + resolution: {integrity: sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==} + + nock@13.5.4: + resolution: {integrity: sha512-yAyTfdeNJGGBFxWdzSKCBYxs5FxLbCg5X5Q4ets974hcQzG1+qCxvIyOo4j2Ry6MUlhWVMX4OoYDefAIIwupjw==} + engines: {node: '>= 10.13'} + + node-abi@3.62.0: + resolution: {integrity: sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==} + engines: {node: '>=10'} + + node-emoji@2.1.3: + resolution: {integrity: sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==} + engines: {node: '>=18'} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-gyp@10.1.0: + resolution: {integrity: sha512-B4J5M1cABxPc5PwfjhbV5hoy2DP9p8lFXASnEN6hugXOa61416tnTZ29x9sSwAd0o99XNIcpvDDy1swAExsVKA==} + engines: {node: ^16.14.0 || >=18.0.0} + hasBin: true + + node-html-parser@6.1.13: + resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==} + + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-preload@0.2.1: + resolution: {integrity: sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==} + engines: {node: '>=8'} + + node-releases@2.0.14: + resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + + nopt@7.2.1: + resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + hasBin: true + + normalize-package-data@2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + + normalize-package-data@3.0.3: + resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} + engines: {node: '>=10'} + + normalize-package-data@6.0.1: + resolution: {integrity: sha512-6rvCfeRW+OEZagAB4lMLSNuTNYZWLVtKccK79VSTf//yTY5VOCgcpH80O+bZK8Neps7pUnd5G+QlMg1yV/2iZQ==} + engines: {node: ^16.14.0 || >=18.0.0} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-url@6.1.0: + resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} + engines: {node: '>=10'} + + normalize-url@8.0.1: + resolution: {integrity: sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==} + engines: {node: '>=14.16'} + + npm-normalize-package-bin@3.0.1: + resolution: {integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + npm-run-all2@6.1.2: + resolution: {integrity: sha512-WwwnS8Ft+RpXve6T2EIEVpFLSqN+ORHRvgNk3H9N62SZXjmzKoRhMFg3I17TK3oMaAEr+XFbRirWS2Fn3BCPSg==} + engines: {node: ^14.18.0 || >=16.0.0, npm: '>= 8'} + hasBin: true + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + npm@10.7.0: + resolution: {integrity: sha512-FXylyYSXNjgXx3l82BT8RSQvCoGIQ3h8YdRFGKNvo3Pv/bKscK4pdWkx/onwTpHDqGw+oeLf4Rxln9WVyxAxlQ==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + bundledDependencies: + - '@isaacs/string-locale-compare' + - '@npmcli/arborist' + - '@npmcli/config' + - '@npmcli/fs' + - '@npmcli/map-workspaces' + - '@npmcli/package-json' + - '@npmcli/promise-spawn' + - '@npmcli/redact' + - '@npmcli/run-script' + - '@sigstore/tuf' + - abbrev + - archy + - cacache + - chalk + - ci-info + - cli-columns + - fastest-levenshtein + - fs-minipass + - glob + - graceful-fs + - hosted-git-info + - ini + - init-package-json + - is-cidr + - json-parse-even-better-errors + - libnpmaccess + - libnpmdiff + - libnpmexec + - libnpmfund + - libnpmhook + - libnpmorg + - libnpmpack + - libnpmpublish + - libnpmsearch + - libnpmteam + - libnpmversion + - make-fetch-happen + - minimatch + - minipass + - minipass-pipeline + - ms + - node-gyp + - nopt + - normalize-package-data + - npm-audit-report + - npm-install-checks + - npm-package-arg + - npm-pick-manifest + - npm-profile + - npm-registry-fetch + - npm-user-validate + - p-map + - pacote + - parse-conflict-json + - proc-log + - qrcode-terminal + - read + - semver + - spdx-expression-parse + - ssri + - supports-color + - tar + - text-table + - tiny-relative-date + - treeverse + - validate-npm-package-name + - which + - write-file-atomic + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + nyc@15.1.0: + resolution: {integrity: sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==} + engines: {node: '>=8.9'} + hasBin: true + + object-inspect@1.13.1: + resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} + + object-is@1.1.6: + resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.5: + resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.0: + resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} + engines: {node: '>= 0.4'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + + openpgp@5.11.1: + resolution: {integrity: sha512-TynUBPuaSI7dN0gP+A38CjNRLxkOkkptefNanalDQ71BFAKKm+dLbksymSW5bUrB7RcAneMySL/Y+r/TbLpOnQ==} + engines: {node: '>= 8.0.0'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-all@3.0.0: + resolution: {integrity: sha512-qUZbvbBFVXm6uJ7U/WDiO0fv6waBMbjlCm4E66oZdRR+egswICarIdHyVSZZHudH8T5SF8x/JG0q0duFzPnlBw==} + engines: {node: '>=10'} + + p-cancelable@2.1.1: + resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} + engines: {node: '>=8'} + + p-each-series@3.0.0: + resolution: {integrity: sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==} + engines: {node: '>=12'} + + p-filter@2.1.0: + resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} + engines: {node: '>=8'} + + p-filter@4.1.0: + resolution: {integrity: sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==} + engines: {node: '>=18'} + + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + + p-is-promise@3.0.0: + resolution: {integrity: sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==} + engines: {node: '>=8'} + + p-limit@1.3.0: + resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} + engines: {node: '>=4'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@2.0.0: + resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} + engines: {node: '>=4'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-map@2.1.0: + resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} + engines: {node: '>=6'} + + p-map@3.0.0: + resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==} + engines: {node: '>=8'} + + p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + + p-map@7.0.2: + resolution: {integrity: sha512-z4cYYMMdKHzw4O5UkWJImbZynVIo0lSGTXc7bzB1e/rrDqkgGUNysK/o4bTr+0+xKvvLoTyGqYC4Fgljy9qe1Q==} + engines: {node: '>=18'} + + p-queue@6.6.2: + resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} + engines: {node: '>=8'} + + p-reduce@3.0.0: + resolution: {integrity: sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==} + engines: {node: '>=12'} + + p-throttle@4.1.1: + resolution: {integrity: sha512-TuU8Ato+pRTPJoDzYD4s7ocJYcNSEZRvlxoq3hcPI2kZDZ49IQ1Wkj7/gDJc3X7XiEAAvRGtDzdXJI0tC3IL1g==} + engines: {node: '>=10'} + + p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + + p-try@1.0.0: + resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} + engines: {node: '>=4'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-hash@4.0.0: + resolution: {integrity: sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==} + engines: {node: '>=8'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-entities@2.0.0: + resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} + + parse-json@4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-json@8.1.0: + resolution: {integrity: sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==} + engines: {node: '>=18'} + + parse-link-header@2.0.0: + resolution: {integrity: sha512-xjU87V0VyHZybn2RrCX5TIFGxTVZE6zqqZWMPlIKiSKuWh/X5WZdt+w1Ki1nXB+8L/KtL+nZ4iq+sfI6MrhhMw==} + + parse-path@7.0.0: + resolution: {integrity: sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==} + + parse-url@8.1.0: + resolution: {integrity: sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==} + + path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-to-regexp@6.2.2: + resolution: {integrity: sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + path-type@5.0.0: + resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==} + engines: {node: '>=12'} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + pgp-utils@0.0.35: + resolution: {integrity: sha512-gCT6EbSTgljgycVa5qGpfRITaLOLbIKsEVRTdsNRgmLMAJpuJNNdrTn/95r8IWo9rFLlccfmGMJXkG9nVDwmrA==} + + picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + + pify@3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} + + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + pkg-conf@2.1.0: + resolution: {integrity: sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==} + engines: {node: '>=4'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + + possible-typed-array-names@1.0.0: + resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + engines: {node: '>= 0.4'} + + prebuild-install@7.1.2: + resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} + engines: {node: '>=10'} + hasBin: true + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.2.5: + resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + proc-log@3.0.0: + resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + proc-log@4.2.0: + resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + process-on-spawn@1.0.0: + resolution: {integrity: sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==} + engines: {node: '>=8'} + + progress@1.1.8: + resolution: {integrity: sha512-UdA8mJ4weIkUBO224tIarHzuHs4HuYiJvsuGT7j/SPQiUJVjYvNDBIPa0hAorduOfjGohB/qHWRa/lrrWX/mXw==} + engines: {node: '>=0.4.0'} + + promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + propagate@2.0.1: + resolution: {integrity: sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==} + engines: {node: '>= 8'} + + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + + protocols@2.0.1: + resolution: {integrity: sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==} + + pump@3.0.0: + resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + + purepack@1.0.6: + resolution: {integrity: sha512-L/e3qq/3m/TrYtINo2aBB98oz6w8VHGyFy+arSKwPMZDUNNw2OaQxYnZO6UIZZw2OnRl2qkxGmuSOEfsuHXJdA==} + engines: {node: '>=0.10.0'} + + qs@6.12.1: + resolution: {integrity: sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + quick-lru@4.0.1: + resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} + engines: {node: '>=8'} + + quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + re2@1.20.11: + resolution: {integrity: sha512-OsGSxGYXQE3Ha9u+YE/bUYKr0BP6tEnSCcwgq7kQzRzLSYXPJQeYKUR1Hung/4RkIje2f2zYjgyr4nL9eeW18g==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + read-package-json-fast@3.0.2: + resolution: {integrity: sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + read-pkg-up@11.0.0: + resolution: {integrity: sha512-LOVbvF1Q0SZdjClSefZ0Nz5z8u+tIE7mV5NibzmE9VYmDe9CaBbAVtz1veOSZbofrdsilxuDAYnFenukZVp8/Q==} + engines: {node: '>=18'} + deprecated: Renamed to read-package-up + + read-pkg-up@7.0.1: + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} + engines: {node: '>=8'} + + read-pkg@5.2.0: + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + engines: {node: '>=8'} + + read-pkg@9.0.1: + resolution: {integrity: sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==} + engines: {node: '>=18'} + + read-yaml-file@2.1.0: + resolution: {integrity: sha512-UkRNRIwnhG+y7hpqnycCL/xbTk7+ia9VuVTC0S+zVbwd65DI9eUpRMfsWIGrCWxTU/mi+JW8cHQCrv+zfCbEPQ==} + engines: {node: '>=10.13'} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + redeyed@2.1.1: + resolution: {integrity: sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==} + + redis@4.6.13: + resolution: {integrity: sha512-MHgkS4B+sPjCXpf+HfdetBwbRz6vCtsceTmw1pHNYJAsYxrfpOP6dz+piJWGos8wqG7qb3vj/Rrc5qOlmInUuA==} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + regexp.prototype.flags@1.5.2: + resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} + engines: {node: '>= 0.4'} + + registry-auth-token@5.0.2: + resolution: {integrity: sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==} + engines: {node: '>=14'} + + release-zalgo@1.0.0: + resolution: {integrity: sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==} + engines: {node: '>=4'} + + remark-github@10.1.0: + resolution: {integrity: sha512-q0BTFb41N6/uXQVkxRwLRTFRfLFPYP+8li26Js5XC0GKritCSaxrftd+t+8sfN+1i9BtmJPUKoS7CZwtccj0Fg==} + + remark-parse@9.0.0: + resolution: {integrity: sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==} + + remark-stringify@9.0.1: + resolution: {integrity: sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg==} + + remark@13.0.0: + resolution: {integrity: sha512-HDz1+IKGtOyWN+QgBiAT0kn+2s6ovOxHyPAFGKVE81VSzJ+mq7RwHFledEvB5F1p4iJvOah/LOKdFuzvRnNLCA==} + + repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-in-the-middle@7.3.0: + resolution: {integrity: sha512-nQFEv9gRw6SJAwWD2LrL0NmQvAcO7FBwJbwmr2ttPAacfy0xuiOjE5zt+zM4xDyuyvUaxBi/9gb2SoCyNEVJcw==} + engines: {node: '>=8.6.0'} + + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + + resolve-alpn@1.2.1: + resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve.exports@2.0.2: + resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} + engines: {node: '>=10'} + + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + + responselike@2.0.1: + resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} + + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@2.4.5: + resolution: {integrity: sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==} + hasBin: true + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + + rimraf@5.0.7: + resolution: {integrity: sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg==} + engines: {node: '>=14.18'} + hasBin: true + + roarr@2.15.4: + resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} + engines: {node: '>=8.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-array-concat@1.1.2: + resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} + engines: {node: '>=0.4'} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-json-stringify@1.2.0: + resolution: {integrity: sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==} + + safe-regex-test@1.0.3: + resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} + engines: {node: '>= 0.4'} + + safe-stable-stringify@2.4.3: + resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==} + engines: {node: '>=10'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sax@1.3.0: + resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==} + + semantic-release@22.0.12: + resolution: {integrity: sha512-0mhiCR/4sZb00RVFJIUlMuiBkW3NMpVIW2Gse7noqEMoFGkvfPPAImEQbkBV8xga4KOPP4FdTRYuLLy32R1fPw==} + engines: {node: ^18.17 || >=20.6.1} + hasBin: true + + semver-compare@1.0.0: + resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + + semver-diff@4.0.0: + resolution: {integrity: sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==} + engines: {node: '>=12'} + + semver-regex@4.0.5: + resolution: {integrity: sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==} + engines: {node: '>=12'} + + semver-stable@3.0.0: + resolution: {integrity: sha512-lolq9k0lqdnZ0C4+QJvrPBWiAHGhLaVSMTPJajn527ptOHAcZnEhj0n2r6ALnryNiRKPO9AIO9iBI3ZSheHCaw==} + engines: {node: '>=0.10.0'} + + semver-utils@1.1.4: + resolution: {integrity: sha512-EjnoLE5OGmDAVV/8YDoN5KiajNadjzIp9BAHOhYeQHt7j0UWxjmgsx4YD48wp4Ue1Qogq38F1GNUJNqF1kKKxA==} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.6.2: + resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} + engines: {node: '>=10'} + hasBin: true + + serialize-error@7.0.1: + resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} + engines: {node: '>=10'} + + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.8.1: + resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} + + shimmer@1.2.1: + resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} + + shlex@2.1.2: + resolution: {integrity: sha512-Nz6gtibMVgYeMEhUjp2KuwAgqaJA1K155dU/HuDaEJUGgnmYfVtVZah+uerVWdH8UGnyahhDCgABbYTbs254+w==} + + side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + signale@1.4.0: + resolution: {integrity: sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==} + engines: {node: '>=6'} + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + simple-git@3.24.0: + resolution: {integrity: sha512-QqAKee9Twv+3k8IFOFfPB2hnk6as6Y6ACUpwCtQvRYBAes23Wv3SZlHVobAzqcE8gfsisCvPw3HGW3HYM+VYYw==} + + sinon@16.1.3: + resolution: {integrity: sha512-mjnWWeyxcAf9nC0bXcPmiDut+oE8HYridTNzBbF98AYVLmWwGRp2ISEpyhYflG1ifILT+eNn3BmKUJPxjXUPlA==} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + skin-tone@2.0.0: + resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==} + engines: {node: '>=8'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slash@5.1.0: + resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} + engines: {node: '>=14.16'} + + slugify@1.6.6: + resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==} + engines: {node: '>=8.0.0'} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@8.0.3: + resolution: {integrity: sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==} + engines: {node: '>= 14'} + + socks@2.8.3: + resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + sonic-forest@1.0.2: + resolution: {integrity: sha512-2rICdwIJi5kVlehMUVtJeHn3ohh5YZV4pDv0P0c1M11cRz/gXNViItpM94HQwfvnXuzybpqK0LZJgTa3lEwtAw==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + sort-keys@4.2.0: + resolution: {integrity: sha512-aUYIEU/UviqPgc8mHR6IW1EGxkAXpeRETYcrzg8cLAvUPZcpAlleSXHV2mY7G12GphSH6Gzv+4MMVSSkbdteHg==} + engines: {node: '>=8'} + + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + spawn-error-forwarder@1.0.0: + resolution: {integrity: sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==} + + spawn-wrap@2.0.0: + resolution: {integrity: sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==} + engines: {node: '>=8'} + + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-license-ids@3.0.17: + resolution: {integrity: sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==} + + split2@1.0.0: + resolution: {integrity: sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==} + + split2@3.2.2: + resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + + ssri@10.0.6: + resolution: {integrity: sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + stop-iteration-iterator@1.0.0: + resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} + engines: {node: '>= 0.4'} + + stream-combiner2@1.1.1: + resolution: {integrity: sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==} + + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string.prototype.trim@1.2.9: + resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.8: + resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + strip-comments-strings@1.2.0: + resolution: {integrity: sha512-zwF4bmnyEjZwRhaak9jUWNxc0DoeKBJ7lwSN/LEc8dQXZcUFG6auaaTQJokQWXopLdM3iTx01nQT8E4aL29DAQ==} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strnum@1.0.5: + resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-hyperlinks@3.0.0: + resolution: {integrity: sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==} + engines: {node: '>=14.18'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + + tar-fs@2.1.1: + resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + + temp-dir@3.0.0: + resolution: {integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==} + engines: {node: '>=14.16'} + + tempy@3.1.0: + resolution: {integrity: sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==} + engines: {node: '>=14.16'} + + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + + text-extensions@2.4.0: + resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} + engines: {node: '>=8'} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + thingies@1.21.0: + resolution: {integrity: sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==} + engines: {node: '>=10.18'} + peerDependencies: + tslib: ^2 + + through2-concurrent@2.0.0: + resolution: {integrity: sha512-R5/jLkfMvdmDD+seLwN7vB+mhbqzWop5fAjx5IX8/yQq7VhBhzDmhXgaHAOnhnWkCpRMM7gToYHycB0CS/pd+A==} + + through2@2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + + through2@4.0.2: + resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + tinylogic@2.0.0: + resolution: {integrity: sha512-dljTkiLLITtsjqBvTA1MRZQK/sGP4kI3UJKc3yA9fMzYbMF2RhcN04SeROVqJBIYYOoJMM8u0WDnhFwMSFQotw==} + + tmp-promise@3.0.3: + resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==} + + tmp@0.2.3: + resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} + engines: {node: '>=14.14'} + + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + + to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toml-eslint-parser@0.9.3: + resolution: {integrity: sha512-moYoCvkNUAPCxSW9jmHmRElhm4tVJpHL8ItC/+uYD0EpPSFXbck7yREz9tNdJVTSpHVod8+HoipcpbQ0oE6gsw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + traverse@0.6.9: + resolution: {integrity: sha512-7bBrcF+/LQzSgFmT0X5YclVqQxtv7TDJ1f8Wj7ibBu/U6BMLeOpUxuZjV7rMc44UtKxlnMFigdhFAIszSX1DMg==} + engines: {node: '>= 0.4'} + + tree-dump@1.0.1: + resolution: {integrity: sha512-WCkcRBVPSlHHq1dc/px9iOfqklvzCbdRwvlNfxGZsrHqf6aZttfPrd7DJTt6oR10dwUfpFFQeVTkPbBIZxX/YA==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + treeify@1.1.0: + resolution: {integrity: sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==} + engines: {node: '>=0.6'} + + trim-newlines@3.0.1: + resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} + engines: {node: '>=8'} + + triplesec@4.0.3: + resolution: {integrity: sha512-fug70e1nJoCMxsXQJlETisAALohm84vl++IiTTHEqM7Lgqwz62jrlwqOC/gJEAJjO/ByN127sEcioB56HW3wIw==} + + trough@1.0.5: + resolution: {integrity: sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==} + + ts-api-utils@1.3.0: + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + ts-essentials@10.0.0: + resolution: {integrity: sha512-77FHNJEyysF9+1s4G6eejuA1lxw7uMchT3ZPy3CIbh7GIunffpshtM8pTe5G6N5dpOzNevqRHew859ceLWVBfw==} + peerDependencies: + typescript: '>=4.5.0' + peerDependenciesMeta: + typescript: + optional: true + + ts-jest@29.1.2: + resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==} + engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + + tsutils@3.21.0: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + 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' + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + tunnel@0.0.6: + resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} + engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} + + tweetnacl@0.13.3: + resolution: {integrity: sha512-iNWodk4oBsZ03Qfw/Yvv0KB90uYrJqvL4Je7Gy4C5t/GS3sCXPRmIT1lxmId4RzvUp0XG62bcxJ2CBu/3L5DSg==} + + tweetnacl@1.0.3: + resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} + + typanion@3.14.0: + resolution: {integrity: sha512-ZW/lVMRabETuYCd9O9ZvMhAh8GslSqaUjxmK/JLPCh6l73CvLBiuXswj/+7LdnWOgYsQ130FqLzFz5aGT4I3Ug==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.13.1: + resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: '>=10'} + + type-fest@0.18.1: + resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} + engines: {node: '>=10'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} + + type-fest@0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + + type-fest@1.4.0: + resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} + engines: {node: '>=10'} + + type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + + type-fest@4.18.2: + resolution: {integrity: sha512-+suCYpfJLAe4OXS6+PPXjW3urOS4IoP9waSiLuXfLgqZODKw/aWwASvzqE886wA0kQgGy0mIWyhd87VpqIy6Xg==} + engines: {node: '>=16'} + + typed-array-buffer@1.0.2: + resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.1: + resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.2: + resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.6: + resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} + engines: {node: '>= 0.4'} + + typed-rest-client@1.8.11: + resolution: {integrity: sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==} + + typedarray-to-buffer@3.1.5: + resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} + + typedarray.prototype.slice@1.0.3: + resolution: {integrity: sha512-8WbVAQAUlENo1q3c3zZYuy5k9VzBQvp8AX9WOtbvyWlLM1v5JaSRmjubLjzHF4JFtptjH/5c/i95yaElvcjC0A==} + engines: {node: '>= 0.4'} + + typescript@5.4.5: + resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} + engines: {node: '>=14.17'} + hasBin: true + + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + + uglify-js@3.17.4: + resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} + engines: {node: '>=0.8.0'} + hasBin: true + + uint64be@1.0.1: + resolution: {integrity: sha512-w+VZSp8hSZ/xWZfZNMppWNF6iqY+dcMYtG5CpwRDgxi94HIE6ematSdkzHGzVC4SDEaTsG65zrajN+oKoWG6ew==} + + unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + + underscore@1.13.6: + resolution: {integrity: sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==} + + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + unicode-emoji-modifier-base@1.0.0: + resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} + engines: {node: '>=4'} + + unicorn-magic@0.1.0: + resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} + engines: {node: '>=18'} + + unified@9.2.2: + resolution: {integrity: sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==} + + unique-filename@3.0.0: + resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + unique-slug@4.0.0: + resolution: {integrity: sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + unique-string@3.0.0: + resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==} + engines: {node: '>=12'} + + unist-util-is@4.1.0: + resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} + + unist-util-stringify-position@2.0.3: + resolution: {integrity: sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==} + + unist-util-visit-parents@3.1.1: + resolution: {integrity: sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==} + + unist-util-visit@2.0.3: + resolution: {integrity: sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==} + + universal-user-agent@6.0.1: + resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + upath@2.0.1: + resolution: {integrity: sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==} + engines: {node: '>=4'} + + update-browserslist-db@1.0.15: + resolution: {integrity: sha512-K9HWH62x3/EalU1U6sjSZiylm9C8tgq2mSvshZpqc7QE69RaA2qjhkW2HlNA0tFpEbtyFz7HTqbSdN4MSwUodA==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + url-join@4.0.1: + resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} + + url-join@5.0.0: + resolution: {integrity: sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + util@0.12.5: + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + v8-to-istanbul@9.2.0: + resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} + engines: {node: '>=10.12.0'} + + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + + validate-npm-package-name@5.0.1: + resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + vfile-message@2.0.4: + resolution: {integrity: sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==} + + vfile@4.2.1: + resolution: {integrity: sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==} + + vuln-vects@1.1.0: + resolution: {integrity: sha512-LGDwn9nRz94YoeqOn2TZqQXzyonBc5FJppSgH34S/1U+3bgPONq/vvfiCbCQ4MeBll58xx+kDmhS73ac+EHBBw==} + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + + which-typed-array@1.1.15: + resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + which@4.0.0: + resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} + engines: {node: ^16.13.0 || >=18.0.0} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@3.0.3: + resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} + + write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + write-file-atomic@5.0.1: + resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + write-yaml-file@4.2.0: + resolution: {integrity: sha512-LwyucHy0uhWqbrOkh9cBluZBeNVxzHjDaE9mwepZG3n3ZlbM4v3ndrFw51zW/NXYFFqP+QWZ72ihtLWTh05e4Q==} + engines: {node: '>=10.13'} + + xmldoc@1.3.0: + resolution: {integrity: sha512-y7IRWW6PvEnYQZNZFMRLNJw+p3pezM4nKYPfr15g4OOW9i8VpeydycFuipE2297OvZnh3jSb2pxOt9QpkZUVng==} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + + zwitch@1.0.5: + resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@arcanis/slice-ansi@1.1.1': + dependencies: + grapheme-splitter: 1.0.4 + + '@aws-crypto/crc32@3.0.0': + dependencies: + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.567.0 + tslib: 1.14.1 + + '@aws-crypto/crc32c@3.0.0': + dependencies: + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.567.0 + tslib: 1.14.1 + + '@aws-crypto/ie11-detection@3.0.0': + dependencies: + tslib: 1.14.1 + + '@aws-crypto/sha1-browser@3.0.0': + dependencies: + '@aws-crypto/ie11-detection': 3.0.0 + '@aws-crypto/supports-web-crypto': 3.0.0 + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.567.0 + '@aws-sdk/util-locate-window': 3.568.0 + '@aws-sdk/util-utf8-browser': 3.259.0 + tslib: 1.14.1 + + '@aws-crypto/sha256-browser@3.0.0': + dependencies: + '@aws-crypto/ie11-detection': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-crypto/supports-web-crypto': 3.0.0 + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.567.0 + '@aws-sdk/util-locate-window': 3.568.0 + '@aws-sdk/util-utf8-browser': 3.259.0 + tslib: 1.14.1 + + '@aws-crypto/sha256-js@3.0.0': + dependencies: + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.567.0 + tslib: 1.14.1 + + '@aws-crypto/supports-web-crypto@3.0.0': + dependencies: + tslib: 1.14.1 + + '@aws-crypto/util@3.0.0': + dependencies: + '@aws-sdk/types': 3.567.0 + '@aws-sdk/util-utf8-browser': 3.259.0 + tslib: 1.14.1 + + '@aws-sdk/client-codecommit@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0))': + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/core': 3.556.0 + '@aws-sdk/credential-provider-node': 3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)) + '@aws-sdk/middleware-host-header': 3.535.0 + '@aws-sdk/middleware-logger': 3.535.0 + '@aws-sdk/middleware-recursion-detection': 3.535.0 + '@aws-sdk/middleware-user-agent': 3.540.0 + '@aws-sdk/region-config-resolver': 3.535.0 + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-endpoints': 3.540.0 + '@aws-sdk/util-user-agent-browser': 3.535.0 + '@aws-sdk/util-user-agent-node': 3.535.0 + '@smithy/config-resolver': 2.2.0 + '@smithy/core': 1.4.2 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/hash-node': 2.2.0 + '@smithy/invalid-dependency': 2.2.0 + '@smithy/middleware-content-length': 2.2.0 + '@smithy/middleware-endpoint': 2.5.1 + '@smithy/middleware-retry': 2.3.1 + '@smithy/middleware-serde': 2.3.0 + '@smithy/middleware-stack': 2.2.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.1 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + '@smithy/util-base64': 2.3.0 + '@smithy/util-body-length-browser': 2.2.0 + '@smithy/util-body-length-node': 2.3.0 + '@smithy/util-defaults-mode-browser': 2.2.1 + '@smithy/util-defaults-mode-node': 2.3.1 + '@smithy/util-endpoints': 1.2.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-retry': 2.2.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + uuid: 9.0.1 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - '@aws-sdk/client-sts' + - aws-crt + + '@aws-sdk/client-cognito-identity@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0))': + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/core': 3.556.0 + '@aws-sdk/credential-provider-node': 3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)) + '@aws-sdk/middleware-host-header': 3.535.0 + '@aws-sdk/middleware-logger': 3.535.0 + '@aws-sdk/middleware-recursion-detection': 3.535.0 + '@aws-sdk/middleware-user-agent': 3.540.0 + '@aws-sdk/region-config-resolver': 3.535.0 + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-endpoints': 3.540.0 + '@aws-sdk/util-user-agent-browser': 3.535.0 + '@aws-sdk/util-user-agent-node': 3.535.0 + '@smithy/config-resolver': 2.2.0 + '@smithy/core': 1.4.2 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/hash-node': 2.2.0 + '@smithy/invalid-dependency': 2.2.0 + '@smithy/middleware-content-length': 2.2.0 + '@smithy/middleware-endpoint': 2.5.1 + '@smithy/middleware-retry': 2.3.1 + '@smithy/middleware-serde': 2.3.0 + '@smithy/middleware-stack': 2.2.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.1 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + '@smithy/util-base64': 2.3.0 + '@smithy/util-body-length-browser': 2.2.0 + '@smithy/util-body-length-node': 2.3.0 + '@smithy/util-defaults-mode-browser': 2.2.1 + '@smithy/util-defaults-mode-node': 2.3.1 + '@smithy/util-endpoints': 1.2.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-retry': 2.2.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - '@aws-sdk/client-sts' + - aws-crt + + '@aws-sdk/client-ec2@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0))': + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/core': 3.556.0 + '@aws-sdk/credential-provider-node': 3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)) + '@aws-sdk/middleware-host-header': 3.535.0 + '@aws-sdk/middleware-logger': 3.535.0 + '@aws-sdk/middleware-recursion-detection': 3.535.0 + '@aws-sdk/middleware-sdk-ec2': 3.556.0 + '@aws-sdk/middleware-user-agent': 3.540.0 + '@aws-sdk/region-config-resolver': 3.535.0 + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-endpoints': 3.540.0 + '@aws-sdk/util-user-agent-browser': 3.535.0 + '@aws-sdk/util-user-agent-node': 3.535.0 + '@smithy/config-resolver': 2.2.0 + '@smithy/core': 1.4.2 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/hash-node': 2.2.0 + '@smithy/invalid-dependency': 2.2.0 + '@smithy/middleware-content-length': 2.2.0 + '@smithy/middleware-endpoint': 2.5.1 + '@smithy/middleware-retry': 2.3.1 + '@smithy/middleware-serde': 2.3.0 + '@smithy/middleware-stack': 2.2.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.1 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + '@smithy/util-base64': 2.3.0 + '@smithy/util-body-length-browser': 2.2.0 + '@smithy/util-body-length-node': 2.3.0 + '@smithy/util-defaults-mode-browser': 2.2.1 + '@smithy/util-defaults-mode-node': 2.3.1 + '@smithy/util-endpoints': 1.2.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-retry': 2.2.0 + '@smithy/util-utf8': 2.3.0 + '@smithy/util-waiter': 2.2.0 + tslib: 2.6.2 + uuid: 9.0.1 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - '@aws-sdk/client-sts' + - aws-crt + + '@aws-sdk/client-ecr@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0))': + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/core': 3.556.0 + '@aws-sdk/credential-provider-node': 3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)) + '@aws-sdk/middleware-host-header': 3.535.0 + '@aws-sdk/middleware-logger': 3.535.0 + '@aws-sdk/middleware-recursion-detection': 3.535.0 + '@aws-sdk/middleware-user-agent': 3.540.0 + '@aws-sdk/region-config-resolver': 3.535.0 + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-endpoints': 3.540.0 + '@aws-sdk/util-user-agent-browser': 3.535.0 + '@aws-sdk/util-user-agent-node': 3.535.0 + '@smithy/config-resolver': 2.2.0 + '@smithy/core': 1.4.2 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/hash-node': 2.2.0 + '@smithy/invalid-dependency': 2.2.0 + '@smithy/middleware-content-length': 2.2.0 + '@smithy/middleware-endpoint': 2.5.1 + '@smithy/middleware-retry': 2.3.1 + '@smithy/middleware-serde': 2.3.0 + '@smithy/middleware-stack': 2.2.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.1 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + '@smithy/util-base64': 2.3.0 + '@smithy/util-body-length-browser': 2.2.0 + '@smithy/util-body-length-node': 2.3.0 + '@smithy/util-defaults-mode-browser': 2.2.1 + '@smithy/util-defaults-mode-node': 2.3.1 + '@smithy/util-endpoints': 1.2.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-retry': 2.2.0 + '@smithy/util-utf8': 2.3.0 + '@smithy/util-waiter': 2.2.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - '@aws-sdk/client-sts' + - aws-crt + + '@aws-sdk/client-rds@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0))': + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/core': 3.556.0 + '@aws-sdk/credential-provider-node': 3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)) + '@aws-sdk/middleware-host-header': 3.535.0 + '@aws-sdk/middleware-logger': 3.535.0 + '@aws-sdk/middleware-recursion-detection': 3.535.0 + '@aws-sdk/middleware-sdk-rds': 3.556.0 + '@aws-sdk/middleware-user-agent': 3.540.0 + '@aws-sdk/region-config-resolver': 3.535.0 + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-endpoints': 3.540.0 + '@aws-sdk/util-user-agent-browser': 3.535.0 + '@aws-sdk/util-user-agent-node': 3.535.0 + '@smithy/config-resolver': 2.2.0 + '@smithy/core': 1.4.2 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/hash-node': 2.2.0 + '@smithy/invalid-dependency': 2.2.0 + '@smithy/middleware-content-length': 2.2.0 + '@smithy/middleware-endpoint': 2.5.1 + '@smithy/middleware-retry': 2.3.1 + '@smithy/middleware-serde': 2.3.0 + '@smithy/middleware-stack': 2.2.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.1 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + '@smithy/util-base64': 2.3.0 + '@smithy/util-body-length-browser': 2.2.0 + '@smithy/util-body-length-node': 2.3.0 + '@smithy/util-defaults-mode-browser': 2.2.1 + '@smithy/util-defaults-mode-node': 2.3.1 + '@smithy/util-endpoints': 1.2.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-retry': 2.2.0 + '@smithy/util-utf8': 2.3.0 + '@smithy/util-waiter': 2.2.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - '@aws-sdk/client-sts' + - aws-crt + + '@aws-sdk/client-s3@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0))': + dependencies: + '@aws-crypto/sha1-browser': 3.0.0 + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/core': 3.556.0 + '@aws-sdk/credential-provider-node': 3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)) + '@aws-sdk/middleware-bucket-endpoint': 3.535.0 + '@aws-sdk/middleware-expect-continue': 3.535.0 + '@aws-sdk/middleware-flexible-checksums': 3.535.0 + '@aws-sdk/middleware-host-header': 3.535.0 + '@aws-sdk/middleware-location-constraint': 3.535.0 + '@aws-sdk/middleware-logger': 3.535.0 + '@aws-sdk/middleware-recursion-detection': 3.535.0 + '@aws-sdk/middleware-sdk-s3': 3.556.0 + '@aws-sdk/middleware-signing': 3.556.0 + '@aws-sdk/middleware-ssec': 3.537.0 + '@aws-sdk/middleware-user-agent': 3.540.0 + '@aws-sdk/region-config-resolver': 3.535.0 + '@aws-sdk/signature-v4-multi-region': 3.556.0 + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-endpoints': 3.540.0 + '@aws-sdk/util-user-agent-browser': 3.535.0 + '@aws-sdk/util-user-agent-node': 3.535.0 + '@aws-sdk/xml-builder': 3.535.0 + '@smithy/config-resolver': 2.2.0 + '@smithy/core': 1.4.2 + '@smithy/eventstream-serde-browser': 2.2.0 + '@smithy/eventstream-serde-config-resolver': 2.2.0 + '@smithy/eventstream-serde-node': 2.2.0 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/hash-blob-browser': 2.2.0 + '@smithy/hash-node': 2.2.0 + '@smithy/hash-stream-node': 2.2.0 + '@smithy/invalid-dependency': 2.2.0 + '@smithy/md5-js': 2.2.0 + '@smithy/middleware-content-length': 2.2.0 + '@smithy/middleware-endpoint': 2.5.1 + '@smithy/middleware-retry': 2.3.1 + '@smithy/middleware-serde': 2.3.0 + '@smithy/middleware-stack': 2.2.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.1 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + '@smithy/util-base64': 2.3.0 + '@smithy/util-body-length-browser': 2.2.0 + '@smithy/util-body-length-node': 2.3.0 + '@smithy/util-defaults-mode-browser': 2.2.1 + '@smithy/util-defaults-mode-node': 2.3.1 + '@smithy/util-endpoints': 1.2.0 + '@smithy/util-retry': 2.2.0 + '@smithy/util-stream': 2.2.0 + '@smithy/util-utf8': 2.3.0 + '@smithy/util-waiter': 2.2.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - '@aws-sdk/client-sts' + - aws-crt + + '@aws-sdk/client-sso-oidc@3.569.0': + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/client-sts': 3.569.0 + '@aws-sdk/core': 3.567.0 + '@aws-sdk/credential-provider-node': 3.569.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.569.0) + '@aws-sdk/middleware-host-header': 3.567.0 + '@aws-sdk/middleware-logger': 3.568.0 + '@aws-sdk/middleware-recursion-detection': 3.567.0 + '@aws-sdk/middleware-user-agent': 3.567.0 + '@aws-sdk/region-config-resolver': 3.567.0 + '@aws-sdk/types': 3.567.0 + '@aws-sdk/util-endpoints': 3.567.0 + '@aws-sdk/util-user-agent-browser': 3.567.0 + '@aws-sdk/util-user-agent-node': 3.568.0 + '@smithy/config-resolver': 2.2.0 + '@smithy/core': 1.4.2 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/hash-node': 2.2.0 + '@smithy/invalid-dependency': 2.2.0 + '@smithy/middleware-content-length': 2.2.0 + '@smithy/middleware-endpoint': 2.5.1 + '@smithy/middleware-retry': 2.3.1 + '@smithy/middleware-serde': 2.3.0 + '@smithy/middleware-stack': 2.2.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.1 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + '@smithy/util-base64': 2.3.0 + '@smithy/util-body-length-browser': 2.2.0 + '@smithy/util-body-length-node': 2.3.0 + '@smithy/util-defaults-mode-browser': 2.2.1 + '@smithy/util-defaults-mode-node': 2.3.1 + '@smithy/util-endpoints': 1.2.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-retry': 2.2.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sso-oidc@3.569.0(@aws-sdk/client-sts@3.569.0)': + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/client-sts': 3.569.0 + '@aws-sdk/core': 3.567.0 + '@aws-sdk/credential-provider-node': 3.569.0(@aws-sdk/client-sso-oidc@3.569.0(@aws-sdk/client-sts@3.569.0))(@aws-sdk/client-sts@3.569.0) + '@aws-sdk/middleware-host-header': 3.567.0 + '@aws-sdk/middleware-logger': 3.568.0 + '@aws-sdk/middleware-recursion-detection': 3.567.0 + '@aws-sdk/middleware-user-agent': 3.567.0 + '@aws-sdk/region-config-resolver': 3.567.0 + '@aws-sdk/types': 3.567.0 + '@aws-sdk/util-endpoints': 3.567.0 + '@aws-sdk/util-user-agent-browser': 3.567.0 + '@aws-sdk/util-user-agent-node': 3.568.0 + '@smithy/config-resolver': 2.2.0 + '@smithy/core': 1.4.2 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/hash-node': 2.2.0 + '@smithy/invalid-dependency': 2.2.0 + '@smithy/middleware-content-length': 2.2.0 + '@smithy/middleware-endpoint': 2.5.1 + '@smithy/middleware-retry': 2.3.1 + '@smithy/middleware-serde': 2.3.0 + '@smithy/middleware-stack': 2.2.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.1 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + '@smithy/util-base64': 2.3.0 + '@smithy/util-body-length-browser': 2.2.0 + '@smithy/util-body-length-node': 2.3.0 + '@smithy/util-defaults-mode-browser': 2.2.1 + '@smithy/util-defaults-mode-node': 2.3.1 + '@smithy/util-endpoints': 1.2.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-retry': 2.2.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sts' + - aws-crt + + '@aws-sdk/client-sso@3.556.0': + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/core': 3.556.0 + '@aws-sdk/middleware-host-header': 3.535.0 + '@aws-sdk/middleware-logger': 3.535.0 + '@aws-sdk/middleware-recursion-detection': 3.535.0 + '@aws-sdk/middleware-user-agent': 3.540.0 + '@aws-sdk/region-config-resolver': 3.535.0 + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-endpoints': 3.540.0 + '@aws-sdk/util-user-agent-browser': 3.535.0 + '@aws-sdk/util-user-agent-node': 3.535.0 + '@smithy/config-resolver': 2.2.0 + '@smithy/core': 1.4.2 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/hash-node': 2.2.0 + '@smithy/invalid-dependency': 2.2.0 + '@smithy/middleware-content-length': 2.2.0 + '@smithy/middleware-endpoint': 2.5.1 + '@smithy/middleware-retry': 2.3.1 + '@smithy/middleware-serde': 2.3.0 + '@smithy/middleware-stack': 2.2.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.1 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + '@smithy/util-base64': 2.3.0 + '@smithy/util-body-length-browser': 2.2.0 + '@smithy/util-body-length-node': 2.3.0 + '@smithy/util-defaults-mode-browser': 2.2.1 + '@smithy/util-defaults-mode-node': 2.3.1 + '@smithy/util-endpoints': 1.2.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-retry': 2.2.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sso@3.568.0': + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/core': 3.567.0 + '@aws-sdk/middleware-host-header': 3.567.0 + '@aws-sdk/middleware-logger': 3.568.0 + '@aws-sdk/middleware-recursion-detection': 3.567.0 + '@aws-sdk/middleware-user-agent': 3.567.0 + '@aws-sdk/region-config-resolver': 3.567.0 + '@aws-sdk/types': 3.567.0 + '@aws-sdk/util-endpoints': 3.567.0 + '@aws-sdk/util-user-agent-browser': 3.567.0 + '@aws-sdk/util-user-agent-node': 3.568.0 + '@smithy/config-resolver': 2.2.0 + '@smithy/core': 1.4.2 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/hash-node': 2.2.0 + '@smithy/invalid-dependency': 2.2.0 + '@smithy/middleware-content-length': 2.2.0 + '@smithy/middleware-endpoint': 2.5.1 + '@smithy/middleware-retry': 2.3.1 + '@smithy/middleware-serde': 2.3.0 + '@smithy/middleware-stack': 2.2.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.1 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + '@smithy/util-base64': 2.3.0 + '@smithy/util-body-length-browser': 2.2.0 + '@smithy/util-body-length-node': 2.3.0 + '@smithy/util-defaults-mode-browser': 2.2.1 + '@smithy/util-defaults-mode-node': 2.3.1 + '@smithy/util-endpoints': 1.2.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-retry': 2.2.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)': + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/core': 3.556.0 + '@aws-sdk/credential-provider-node': 3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)) + '@aws-sdk/middleware-host-header': 3.535.0 + '@aws-sdk/middleware-logger': 3.535.0 + '@aws-sdk/middleware-recursion-detection': 3.535.0 + '@aws-sdk/middleware-user-agent': 3.540.0 + '@aws-sdk/region-config-resolver': 3.535.0 + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-endpoints': 3.540.0 + '@aws-sdk/util-user-agent-browser': 3.535.0 + '@aws-sdk/util-user-agent-node': 3.535.0 + '@smithy/config-resolver': 2.2.0 + '@smithy/core': 1.4.2 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/hash-node': 2.2.0 + '@smithy/invalid-dependency': 2.2.0 + '@smithy/middleware-content-length': 2.2.0 + '@smithy/middleware-endpoint': 2.5.1 + '@smithy/middleware-retry': 2.3.1 + '@smithy/middleware-serde': 2.3.0 + '@smithy/middleware-stack': 2.2.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.1 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + '@smithy/util-base64': 2.3.0 + '@smithy/util-body-length-browser': 2.2.0 + '@smithy/util-body-length-node': 2.3.0 + '@smithy/util-defaults-mode-browser': 2.2.1 + '@smithy/util-defaults-mode-node': 2.3.1 + '@smithy/util-endpoints': 1.2.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-retry': 2.2.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + + '@aws-sdk/client-sts@3.569.0': + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/client-sso-oidc': 3.569.0(@aws-sdk/client-sts@3.569.0) + '@aws-sdk/core': 3.567.0 + '@aws-sdk/credential-provider-node': 3.569.0(@aws-sdk/client-sso-oidc@3.569.0(@aws-sdk/client-sts@3.569.0))(@aws-sdk/client-sts@3.569.0) + '@aws-sdk/middleware-host-header': 3.567.0 + '@aws-sdk/middleware-logger': 3.568.0 + '@aws-sdk/middleware-recursion-detection': 3.567.0 + '@aws-sdk/middleware-user-agent': 3.567.0 + '@aws-sdk/region-config-resolver': 3.567.0 + '@aws-sdk/types': 3.567.0 + '@aws-sdk/util-endpoints': 3.567.0 + '@aws-sdk/util-user-agent-browser': 3.567.0 + '@aws-sdk/util-user-agent-node': 3.568.0 + '@smithy/config-resolver': 2.2.0 + '@smithy/core': 1.4.2 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/hash-node': 2.2.0 + '@smithy/invalid-dependency': 2.2.0 + '@smithy/middleware-content-length': 2.2.0 + '@smithy/middleware-endpoint': 2.5.1 + '@smithy/middleware-retry': 2.3.1 + '@smithy/middleware-serde': 2.3.0 + '@smithy/middleware-stack': 2.2.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.1 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + '@smithy/util-base64': 2.3.0 + '@smithy/util-body-length-browser': 2.2.0 + '@smithy/util-body-length-node': 2.3.0 + '@smithy/util-defaults-mode-browser': 2.2.1 + '@smithy/util-defaults-mode-node': 2.3.1 + '@smithy/util-endpoints': 1.2.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-retry': 2.2.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/core@3.556.0': + dependencies: + '@smithy/core': 1.4.2 + '@smithy/protocol-http': 3.3.0 + '@smithy/signature-v4': 2.3.0 + '@smithy/smithy-client': 2.5.1 + '@smithy/types': 2.12.0 + fast-xml-parser: 4.2.5 + tslib: 2.6.2 + + '@aws-sdk/core@3.567.0': + dependencies: + '@smithy/core': 1.4.2 + '@smithy/protocol-http': 3.3.0 + '@smithy/signature-v4': 2.3.0 + '@smithy/smithy-client': 2.5.1 + '@smithy/types': 2.12.0 + fast-xml-parser: 4.2.5 + tslib: 2.6.2 + + '@aws-sdk/credential-provider-cognito-identity@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0))': + dependencies: + '@aws-sdk/client-cognito-identity': 3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)) + '@aws-sdk/types': 3.535.0 + '@smithy/property-provider': 2.2.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - '@aws-sdk/client-sts' + - aws-crt + + '@aws-sdk/credential-provider-env@3.535.0': + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/property-provider': 2.2.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + + '@aws-sdk/credential-provider-env@3.568.0': + dependencies: + '@aws-sdk/types': 3.567.0 + '@smithy/property-provider': 2.2.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + + '@aws-sdk/credential-provider-http@3.552.0': + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/property-provider': 2.2.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.1 + '@smithy/types': 2.12.0 + '@smithy/util-stream': 2.2.0 + tslib: 2.6.2 + + '@aws-sdk/credential-provider-http@3.568.0': + dependencies: + '@aws-sdk/types': 3.567.0 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/property-provider': 2.2.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.1 + '@smithy/types': 2.12.0 + '@smithy/util-stream': 2.2.0 + tslib: 2.6.2 + + '@aws-sdk/credential-provider-ini@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0))': + dependencies: + '@aws-sdk/client-sts': 3.565.0(@aws-sdk/client-sso-oidc@3.569.0) + '@aws-sdk/credential-provider-env': 3.535.0 + '@aws-sdk/credential-provider-process': 3.535.0 + '@aws-sdk/credential-provider-sso': 3.565.0(@aws-sdk/client-sso-oidc@3.569.0) + '@aws-sdk/credential-provider-web-identity': 3.565.0(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)) + '@aws-sdk/types': 3.535.0 + '@smithy/credential-provider-imds': 2.3.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + + '@aws-sdk/credential-provider-ini@3.568.0(@aws-sdk/client-sso-oidc@3.569.0(@aws-sdk/client-sts@3.569.0))(@aws-sdk/client-sts@3.569.0)': + dependencies: + '@aws-sdk/client-sts': 3.569.0 + '@aws-sdk/credential-provider-env': 3.568.0 + '@aws-sdk/credential-provider-process': 3.568.0 + '@aws-sdk/credential-provider-sso': 3.568.0(@aws-sdk/client-sso-oidc@3.569.0(@aws-sdk/client-sts@3.569.0)) + '@aws-sdk/credential-provider-web-identity': 3.568.0(@aws-sdk/client-sts@3.569.0) + '@aws-sdk/types': 3.567.0 + '@smithy/credential-provider-imds': 2.3.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + + '@aws-sdk/credential-provider-ini@3.568.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.569.0)': + dependencies: + '@aws-sdk/client-sts': 3.569.0 + '@aws-sdk/credential-provider-env': 3.568.0 + '@aws-sdk/credential-provider-process': 3.568.0 + '@aws-sdk/credential-provider-sso': 3.568.0(@aws-sdk/client-sso-oidc@3.569.0) + '@aws-sdk/credential-provider-web-identity': 3.568.0(@aws-sdk/client-sts@3.569.0) + '@aws-sdk/types': 3.567.0 + '@smithy/credential-provider-imds': 2.3.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + + '@aws-sdk/credential-provider-node@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0))': + dependencies: + '@aws-sdk/credential-provider-env': 3.535.0 + '@aws-sdk/credential-provider-http': 3.552.0 + '@aws-sdk/credential-provider-ini': 3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)) + '@aws-sdk/credential-provider-process': 3.535.0 + '@aws-sdk/credential-provider-sso': 3.565.0(@aws-sdk/client-sso-oidc@3.569.0) + '@aws-sdk/credential-provider-web-identity': 3.565.0(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)) + '@aws-sdk/types': 3.535.0 + '@smithy/credential-provider-imds': 2.3.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - '@aws-sdk/client-sts' + - aws-crt + + '@aws-sdk/credential-provider-node@3.569.0(@aws-sdk/client-sso-oidc@3.569.0(@aws-sdk/client-sts@3.569.0))(@aws-sdk/client-sts@3.569.0)': + dependencies: + '@aws-sdk/credential-provider-env': 3.568.0 + '@aws-sdk/credential-provider-http': 3.568.0 + '@aws-sdk/credential-provider-ini': 3.568.0(@aws-sdk/client-sso-oidc@3.569.0(@aws-sdk/client-sts@3.569.0))(@aws-sdk/client-sts@3.569.0) + '@aws-sdk/credential-provider-process': 3.568.0 + '@aws-sdk/credential-provider-sso': 3.568.0(@aws-sdk/client-sso-oidc@3.569.0(@aws-sdk/client-sts@3.569.0)) + '@aws-sdk/credential-provider-web-identity': 3.568.0(@aws-sdk/client-sts@3.569.0) + '@aws-sdk/types': 3.567.0 + '@smithy/credential-provider-imds': 2.3.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - '@aws-sdk/client-sts' + - aws-crt + + '@aws-sdk/credential-provider-node@3.569.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.569.0)': + dependencies: + '@aws-sdk/credential-provider-env': 3.568.0 + '@aws-sdk/credential-provider-http': 3.568.0 + '@aws-sdk/credential-provider-ini': 3.568.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.569.0) + '@aws-sdk/credential-provider-process': 3.568.0 + '@aws-sdk/credential-provider-sso': 3.568.0(@aws-sdk/client-sso-oidc@3.569.0) + '@aws-sdk/credential-provider-web-identity': 3.568.0(@aws-sdk/client-sts@3.569.0) + '@aws-sdk/types': 3.567.0 + '@smithy/credential-provider-imds': 2.3.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - '@aws-sdk/client-sts' + - aws-crt + + '@aws-sdk/credential-provider-process@3.535.0': + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + + '@aws-sdk/credential-provider-process@3.568.0': + dependencies: + '@aws-sdk/types': 3.567.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + + '@aws-sdk/credential-provider-sso@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)': + dependencies: + '@aws-sdk/client-sso': 3.556.0 + '@aws-sdk/token-providers': 3.565.0(@aws-sdk/client-sso-oidc@3.569.0) + '@aws-sdk/types': 3.535.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + + '@aws-sdk/credential-provider-sso@3.568.0(@aws-sdk/client-sso-oidc@3.569.0(@aws-sdk/client-sts@3.569.0))': + dependencies: + '@aws-sdk/client-sso': 3.568.0 + '@aws-sdk/token-providers': 3.568.0(@aws-sdk/client-sso-oidc@3.569.0(@aws-sdk/client-sts@3.569.0)) + '@aws-sdk/types': 3.567.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + + '@aws-sdk/credential-provider-sso@3.568.0(@aws-sdk/client-sso-oidc@3.569.0)': dependencies: - '@aws-sdk/types': 3.357.0 - '@smithy/protocol-http': 1.2.0 - '@smithy/signature-v4': 1.1.0 - '@smithy/types': 1.2.0 + '@aws-sdk/client-sso': 3.568.0 + '@aws-sdk/token-providers': 3.568.0(@aws-sdk/client-sso-oidc@3.569.0) + '@aws-sdk/types': 3.567.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt - /@aws-sdk/token-providers@3.363.0: - resolution: {integrity: sha512-6+0aJ1zugNgsMmhTtW2LBWxOVSaXCUk2q3xyTchSXkNzallYaRiZMRkieW+pKNntnu0g5H1T0zyfCO0tbXwxEA==} - engines: {node: '>=14.0.0'} + '@aws-sdk/credential-provider-web-identity@3.565.0(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0))': + dependencies: + '@aws-sdk/client-sts': 3.565.0(@aws-sdk/client-sso-oidc@3.569.0) + '@aws-sdk/types': 3.535.0 + '@smithy/property-provider': 2.2.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + + '@aws-sdk/credential-provider-web-identity@3.568.0(@aws-sdk/client-sts@3.569.0)': dependencies: - '@aws-sdk/client-sso-oidc': 3.363.0 - '@aws-sdk/types': 3.357.0 - '@smithy/property-provider': 1.2.0 - '@smithy/shared-ini-file-loader': 1.1.0 - '@smithy/types': 1.2.0 + '@aws-sdk/client-sts': 3.569.0 + '@aws-sdk/types': 3.567.0 + '@smithy/property-provider': 2.2.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + + '@aws-sdk/credential-providers@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)': + dependencies: + '@aws-sdk/client-cognito-identity': 3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)) + '@aws-sdk/client-sso': 3.556.0 + '@aws-sdk/client-sts': 3.565.0(@aws-sdk/client-sso-oidc@3.569.0) + '@aws-sdk/credential-provider-cognito-identity': 3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)) + '@aws-sdk/credential-provider-env': 3.535.0 + '@aws-sdk/credential-provider-http': 3.552.0 + '@aws-sdk/credential-provider-ini': 3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)) + '@aws-sdk/credential-provider-node': 3.565.0(@aws-sdk/client-sso-oidc@3.569.0)(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)) + '@aws-sdk/credential-provider-process': 3.535.0 + '@aws-sdk/credential-provider-sso': 3.565.0(@aws-sdk/client-sso-oidc@3.569.0) + '@aws-sdk/credential-provider-web-identity': 3.565.0(@aws-sdk/client-sts@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)) + '@aws-sdk/types': 3.535.0 + '@smithy/credential-provider-imds': 2.3.0 + '@smithy/property-provider': 2.2.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' - aws-crt - dev: false - /@aws-sdk/types@3.357.0: - resolution: {integrity: sha512-/riCRaXg3p71BeWnShrai0y0QTdXcouPSM0Cn1olZbzTf7s71aLEewrc96qFrL70XhY4XvnxMpqQh+r43XIL3g==} - engines: {node: '>=14.0.0'} + '@aws-sdk/middleware-bucket-endpoint@3.535.0': dependencies: + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-arn-parser': 3.535.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/types': 2.12.0 + '@smithy/util-config-provider': 2.3.0 tslib: 2.6.2 - dev: false - /@aws-sdk/util-arn-parser@3.310.0: - resolution: {integrity: sha512-jL8509owp/xB9+Or0pvn3Fe+b94qfklc2yPowZZIFAkFcCSIdkIglz18cPDWnYAcy9JGewpMS1COXKIUhZkJsA==} - engines: {node: '>=14.0.0'} + '@aws-sdk/middleware-expect-continue@3.535.0': dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@aws-sdk/util-buffer-from@3.310.0: - resolution: {integrity: sha512-i6LVeXFtGih5Zs8enLrt+ExXY92QV25jtEnTKHsmlFqFAuL3VBeod6boeMXkN2p9lbSVVQ1sAOOYZOHYbYkntw==} - engines: {node: '>=14.0.0'} + '@aws-sdk/middleware-flexible-checksums@3.535.0': dependencies: - '@aws-sdk/is-array-buffer': 3.310.0 + '@aws-crypto/crc32': 3.0.0 + '@aws-crypto/crc32c': 3.0.0 + '@aws-sdk/types': 3.535.0 + '@smithy/is-array-buffer': 2.2.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/types': 2.12.0 + '@smithy/util-utf8': 2.3.0 tslib: 2.6.2 - dev: false - /@aws-sdk/util-endpoints@3.357.0: - resolution: {integrity: sha512-XHKyS5JClT9su9hDif715jpZiWHQF9gKZXER8tW0gOizU3R9cyWc9EsJ2BRhFNhi7nt/JF/CLUEc5qDx3ETbUw==} - engines: {node: '>=14.0.0'} + '@aws-sdk/middleware-host-header@3.535.0': dependencies: - '@aws-sdk/types': 3.357.0 + '@aws-sdk/types': 3.535.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@aws-sdk/util-format-url@3.363.0: - resolution: {integrity: sha512-lz3z4M5QPVJxALVUOU6a538CO+VPDLl9dgeAQCRF7fVjYP7nRGjm7Hc1oiEP7nYL3sAw/lQfbL7NlTPNXRwRlQ==} - engines: {node: '>=14.0.0'} + '@aws-sdk/middleware-host-header@3.567.0': dependencies: - '@aws-sdk/types': 3.357.0 - '@smithy/querystring-builder': 1.1.0 - '@smithy/types': 1.2.0 + '@aws-sdk/types': 3.567.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@aws-sdk/util-locate-window@3.495.0: - resolution: {integrity: sha512-MfaPXT0kLX2tQaR90saBT9fWQq2DHqSSJRzW+MZWsmF+y5LGCOhO22ac/2o6TKSQm7h0HRc2GaADqYYYor62yg==} - engines: {node: '>=14.0.0'} + '@aws-sdk/middleware-location-constraint@3.535.0': + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-logger@3.535.0': + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-logger@3.568.0': + dependencies: + '@aws-sdk/types': 3.567.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-recursion-detection@3.535.0': + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-recursion-detection@3.567.0': + dependencies: + '@aws-sdk/types': 3.567.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-sdk-ec2@3.556.0': + dependencies: + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-format-url': 3.535.0 + '@smithy/middleware-endpoint': 2.5.1 + '@smithy/protocol-http': 3.3.0 + '@smithy/signature-v4': 2.3.0 + '@smithy/smithy-client': 2.5.1 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-sdk-rds@3.556.0': + dependencies: + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-format-url': 3.535.0 + '@smithy/middleware-endpoint': 2.5.1 + '@smithy/protocol-http': 3.3.0 + '@smithy/signature-v4': 2.3.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-sdk-s3@3.556.0': + dependencies: + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-arn-parser': 3.535.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/signature-v4': 2.3.0 + '@smithy/smithy-client': 2.5.1 + '@smithy/types': 2.12.0 + '@smithy/util-config-provider': 2.3.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-signing@3.556.0': + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/property-provider': 2.2.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/signature-v4': 2.3.0 + '@smithy/types': 2.12.0 + '@smithy/util-middleware': 2.2.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-ssec@3.537.0': + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-user-agent@3.540.0': + dependencies: + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-endpoints': 3.540.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-user-agent@3.567.0': + dependencies: + '@aws-sdk/types': 3.567.0 + '@aws-sdk/util-endpoints': 3.567.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + + '@aws-sdk/region-config-resolver@3.535.0': + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/types': 2.12.0 + '@smithy/util-config-provider': 2.3.0 + '@smithy/util-middleware': 2.2.0 + tslib: 2.6.2 + + '@aws-sdk/region-config-resolver@3.567.0': + dependencies: + '@aws-sdk/types': 3.567.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/types': 2.12.0 + '@smithy/util-config-provider': 2.3.0 + '@smithy/util-middleware': 2.2.0 + tslib: 2.6.2 + + '@aws-sdk/signature-v4-multi-region@3.556.0': + dependencies: + '@aws-sdk/middleware-sdk-s3': 3.556.0 + '@aws-sdk/types': 3.535.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/signature-v4': 2.3.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + + '@aws-sdk/token-providers@3.565.0(@aws-sdk/client-sso-oidc@3.569.0)': + dependencies: + '@aws-sdk/client-sso-oidc': 3.569.0 + '@aws-sdk/types': 3.535.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + + '@aws-sdk/token-providers@3.568.0(@aws-sdk/client-sso-oidc@3.569.0(@aws-sdk/client-sts@3.569.0))': dependencies: + '@aws-sdk/client-sso-oidc': 3.569.0(@aws-sdk/client-sts@3.569.0) + '@aws-sdk/types': 3.567.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@aws-sdk/util-user-agent-browser@3.363.0: - resolution: {integrity: sha512-fk9ymBUIYbxiGm99Cn+kAAXmvMCWTf/cHAcB79oCXV4ELXdPa9lN5xQhZRFNxLUeXG4OAMEuCAUUuZEj8Fnc1Q==} + '@aws-sdk/token-providers@3.568.0(@aws-sdk/client-sso-oidc@3.569.0)': dependencies: - '@aws-sdk/types': 3.357.0 - '@smithy/types': 1.2.0 + '@aws-sdk/client-sso-oidc': 3.569.0 + '@aws-sdk/types': 3.567.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + + '@aws-sdk/types@3.535.0': + dependencies: + '@smithy/types': 2.12.0 + tslib: 2.6.2 + + '@aws-sdk/types@3.567.0': + dependencies: + '@smithy/types': 2.12.0 + tslib: 2.6.2 + + '@aws-sdk/util-arn-parser@3.535.0': + dependencies: + tslib: 2.6.2 + + '@aws-sdk/util-endpoints@3.540.0': + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/types': 2.12.0 + '@smithy/util-endpoints': 1.2.0 + tslib: 2.6.2 + + '@aws-sdk/util-endpoints@3.567.0': + dependencies: + '@aws-sdk/types': 3.567.0 + '@smithy/types': 2.12.0 + '@smithy/util-endpoints': 1.2.0 + tslib: 2.6.2 + + '@aws-sdk/util-format-url@3.535.0': + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/querystring-builder': 2.2.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + + '@aws-sdk/util-locate-window@3.568.0': + dependencies: + tslib: 2.6.2 + + '@aws-sdk/util-user-agent-browser@3.535.0': + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/types': 2.12.0 bowser: 2.11.0 tslib: 2.6.2 - dev: false - /@aws-sdk/util-user-agent-node@3.363.0: - resolution: {integrity: sha512-Fli/dvgGA9hdnQUrYb1//wNSFlK2jAfdJcfNXA6SeBYzSeH5pVGYF4kXF0FCdnMA3Fef+Zn1zAP/hw9v8VJHWQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - aws-crt: '>=1.0.0' - peerDependenciesMeta: - aws-crt: - optional: true + '@aws-sdk/util-user-agent-browser@3.567.0': dependencies: - '@aws-sdk/types': 3.357.0 - '@smithy/node-config-provider': 1.1.0 - '@smithy/types': 1.2.0 + '@aws-sdk/types': 3.567.0 + '@smithy/types': 2.12.0 + bowser: 2.11.0 tslib: 2.6.2 - dev: false - /@aws-sdk/util-utf8-browser@3.259.0: - resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} + '@aws-sdk/util-user-agent-node@3.535.0': dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@aws-sdk/util-utf8@3.310.0: - resolution: {integrity: sha512-DnLfFT8uCO22uOJc0pt0DsSNau1GTisngBCDw8jQuWT5CqogMJu4b/uXmwEqfj8B3GX6Xsz8zOd6JpRlPftQoA==} - engines: {node: '>=14.0.0'} + '@aws-sdk/util-user-agent-node@3.568.0': dependencies: - '@aws-sdk/util-buffer-from': 3.310.0 + '@aws-sdk/types': 3.567.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@aws-sdk/xml-builder@3.310.0: - resolution: {integrity: sha512-TqELu4mOuSIKQCqj63fGVs86Yh+vBx5nHRpWKNUNhB2nPTpfbziTs5c1X358be3peVWA4wPxW7Nt53KIg1tnNw==} - engines: {node: '>=14.0.0'} + '@aws-sdk/util-utf8-browser@3.259.0': dependencies: tslib: 2.6.2 - dev: false - /@babel/code-frame@7.23.5: - resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} - engines: {node: '>=6.9.0'} + '@aws-sdk/xml-builder@3.535.0': dependencies: - '@babel/highlight': 7.23.4 - chalk: 2.4.2 + '@smithy/types': 2.12.0 + tslib: 2.6.2 - /@babel/compat-data@7.23.5: - resolution: {integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==} - engines: {node: '>=6.9.0'} - dev: true + '@babel/code-frame@7.24.2': + dependencies: + '@babel/highlight': 7.24.5 + picocolors: 1.0.0 - /@babel/core@7.23.9: - resolution: {integrity: sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==} - engines: {node: '>=6.9.0'} + '@babel/compat-data@7.24.4': {} + + '@babel/core@7.24.5': dependencies: - '@ampproject/remapping': 2.2.1 - '@babel/code-frame': 7.23.5 - '@babel/generator': 7.23.6 + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.24.2 + '@babel/generator': 7.24.5 '@babel/helper-compilation-targets': 7.23.6 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.9) - '@babel/helpers': 7.23.9 - '@babel/parser': 7.23.9 - '@babel/template': 7.23.9 - '@babel/traverse': 7.23.9 - '@babel/types': 7.23.9 + '@babel/helper-module-transforms': 7.24.5(@babel/core@7.24.5) + '@babel/helpers': 7.24.5 + '@babel/parser': 7.24.5 + '@babel/template': 7.24.0 + '@babel/traverse': 7.24.5 + '@babel/types': 7.24.5 convert-source-map: 2.0.0 debug: 4.3.4 gensync: 1.0.0-beta.2 @@ -1601,350 +7251,206 @@ packages: semver: 6.3.1 transitivePeerDependencies: - supports-color - dev: true - /@babel/generator@7.23.6: - resolution: {integrity: sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==} - engines: {node: '>=6.9.0'} + '@babel/generator@7.24.5': dependencies: - '@babel/types': 7.23.9 - '@jridgewell/gen-mapping': 0.3.4 - '@jridgewell/trace-mapping': 0.3.23 + '@babel/types': 7.24.5 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 - dev: true - /@babel/helper-compilation-targets@7.23.6: - resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==} - engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.23.6': dependencies: - '@babel/compat-data': 7.23.5 + '@babel/compat-data': 7.24.4 '@babel/helper-validator-option': 7.23.5 browserslist: 4.23.0 lru-cache: 5.1.1 semver: 6.3.1 - dev: true - /@babel/helper-environment-visitor@7.22.20: - resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} - engines: {node: '>=6.9.0'} - dev: true + '@babel/helper-environment-visitor@7.22.20': {} - /@babel/helper-function-name@7.23.0: - resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} - engines: {node: '>=6.9.0'} + '@babel/helper-function-name@7.23.0': dependencies: - '@babel/template': 7.23.9 - '@babel/types': 7.23.9 - dev: true + '@babel/template': 7.24.0 + '@babel/types': 7.24.5 - /@babel/helper-hoist-variables@7.22.5: - resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} - engines: {node: '>=6.9.0'} + '@babel/helper-hoist-variables@7.22.5': dependencies: - '@babel/types': 7.23.9 - dev: true + '@babel/types': 7.24.5 - /@babel/helper-module-imports@7.22.15: - resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==} - engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.24.3': dependencies: - '@babel/types': 7.23.9 - dev: true + '@babel/types': 7.24.5 - /@babel/helper-module-transforms@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + '@babel/helper-module-transforms@7.24.5(@babel/core@7.24.5)': dependencies: - '@babel/core': 7.23.9 + '@babel/core': 7.24.5 '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-module-imports': 7.22.15 - '@babel/helper-simple-access': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - '@babel/helper-validator-identifier': 7.22.20 - dev: true - - /@babel/helper-plugin-utils@7.22.5: - resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==} - engines: {node: '>=6.9.0'} - dev: true + '@babel/helper-module-imports': 7.24.3 + '@babel/helper-simple-access': 7.24.5 + '@babel/helper-split-export-declaration': 7.24.5 + '@babel/helper-validator-identifier': 7.24.5 - /@babel/helper-simple-access@7.22.5: - resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} - engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@7.24.5': {} + + '@babel/helper-simple-access@7.24.5': dependencies: - '@babel/types': 7.23.9 - dev: true + '@babel/types': 7.24.5 - /@babel/helper-split-export-declaration@7.22.6: - resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} - engines: {node: '>=6.9.0'} + '@babel/helper-split-export-declaration@7.24.5': dependencies: - '@babel/types': 7.23.9 - dev: true + '@babel/types': 7.24.5 - /@babel/helper-string-parser@7.23.4: - resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==} - engines: {node: '>=6.9.0'} - dev: true + '@babel/helper-string-parser@7.24.1': {} - /@babel/helper-validator-identifier@7.22.20: - resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} - engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.24.5': {} - /@babel/helper-validator-option@7.23.5: - resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} - engines: {node: '>=6.9.0'} - dev: true + '@babel/helper-validator-option@7.23.5': {} - /@babel/helpers@7.23.9: - resolution: {integrity: sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==} - engines: {node: '>=6.9.0'} + '@babel/helpers@7.24.5': dependencies: - '@babel/template': 7.23.9 - '@babel/traverse': 7.23.9 - '@babel/types': 7.23.9 + '@babel/template': 7.24.0 + '@babel/traverse': 7.24.5 + '@babel/types': 7.24.5 transitivePeerDependencies: - supports-color - dev: true - /@babel/highlight@7.23.4: - resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==} - engines: {node: '>=6.9.0'} + '@babel/highlight@7.24.5': dependencies: - '@babel/helper-validator-identifier': 7.22.20 + '@babel/helper-validator-identifier': 7.24.5 chalk: 2.4.2 js-tokens: 4.0.0 + picocolors: 1.0.0 - /@babel/parser@7.23.9: - resolution: {integrity: sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==} - engines: {node: '>=6.0.0'} - hasBin: true + '@babel/parser@7.24.5': dependencies: - '@babel/types': 7.23.9 - dev: true + '@babel/types': 7.24.5 - /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.9): - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.5)': dependencies: - '@babel/core': 7.23.9 - '@babel/helper-plugin-utils': 7.22.5 - dev: true + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.24.5 - /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.23.9): - resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.24.5)': dependencies: - '@babel/core': 7.23.9 - '@babel/helper-plugin-utils': 7.22.5 - dev: true + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.24.5 - /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.9): - resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.5)': dependencies: - '@babel/core': 7.23.9 - '@babel/helper-plugin-utils': 7.22.5 - dev: true + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.24.5 - /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.9): - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.5)': dependencies: - '@babel/core': 7.23.9 - '@babel/helper-plugin-utils': 7.22.5 - dev: true + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.24.5 - /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.9): - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.5)': dependencies: - '@babel/core': 7.23.9 - '@babel/helper-plugin-utils': 7.22.5 - dev: true + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.24.5 - /@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-jsx@7.24.1(@babel/core@7.24.5)': dependencies: - '@babel/core': 7.23.9 - '@babel/helper-plugin-utils': 7.22.5 - dev: true + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.24.5 - /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.9): - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.5)': dependencies: - '@babel/core': 7.23.9 - '@babel/helper-plugin-utils': 7.22.5 - dev: true + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.24.5 - /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.9): - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.5)': dependencies: - '@babel/core': 7.23.9 - '@babel/helper-plugin-utils': 7.22.5 - dev: true + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.24.5 - /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.9): - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.5)': dependencies: - '@babel/core': 7.23.9 - '@babel/helper-plugin-utils': 7.22.5 - dev: true + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.24.5 - /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.9): - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.5)': dependencies: - '@babel/core': 7.23.9 - '@babel/helper-plugin-utils': 7.22.5 - dev: true + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.24.5 - /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.9): - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.5)': dependencies: - '@babel/core': 7.23.9 - '@babel/helper-plugin-utils': 7.22.5 - dev: true + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.24.5 - /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.9): - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.5)': dependencies: - '@babel/core': 7.23.9 - '@babel/helper-plugin-utils': 7.22.5 - dev: true + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.24.5 - /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.9): - resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.5)': dependencies: - '@babel/core': 7.23.9 - '@babel/helper-plugin-utils': 7.22.5 - dev: true + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.24.5 - /@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-typescript@7.24.1(@babel/core@7.24.5)': dependencies: - '@babel/core': 7.23.9 - '@babel/helper-plugin-utils': 7.22.5 - dev: true + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.24.5 - /@babel/runtime-corejs3@7.23.9: - resolution: {integrity: sha512-oeOFTrYWdWXCvXGB5orvMTJ6gCZ9I6FBjR+M38iKNXCsPxr4xT0RTdg5uz1H7QP8pp74IzPtwritEr+JscqHXQ==} - engines: {node: '>=6.9.0'} + '@babel/runtime-corejs3@7.24.5': dependencies: - core-js-pure: 3.36.0 + core-js-pure: 3.37.0 regenerator-runtime: 0.14.1 - dev: false - /@babel/template@7.23.9: - resolution: {integrity: sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==} - engines: {node: '>=6.9.0'} + '@babel/template@7.24.0': dependencies: - '@babel/code-frame': 7.23.5 - '@babel/parser': 7.23.9 - '@babel/types': 7.23.9 - dev: true + '@babel/code-frame': 7.24.2 + '@babel/parser': 7.24.5 + '@babel/types': 7.24.5 - /@babel/traverse@7.23.9: - resolution: {integrity: sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==} - engines: {node: '>=6.9.0'} + '@babel/traverse@7.24.5': dependencies: - '@babel/code-frame': 7.23.5 - '@babel/generator': 7.23.6 + '@babel/code-frame': 7.24.2 + '@babel/generator': 7.24.5 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-function-name': 7.23.0 '@babel/helper-hoist-variables': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - '@babel/parser': 7.23.9 - '@babel/types': 7.23.9 + '@babel/helper-split-export-declaration': 7.24.5 + '@babel/parser': 7.24.5 + '@babel/types': 7.24.5 debug: 4.3.4 globals: 11.12.0 transitivePeerDependencies: - supports-color - dev: true - /@babel/types@7.23.9: - resolution: {integrity: sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==} - engines: {node: '>=6.9.0'} + '@babel/types@7.24.5': dependencies: - '@babel/helper-string-parser': 7.23.4 - '@babel/helper-validator-identifier': 7.22.20 + '@babel/helper-string-parser': 7.24.1 + '@babel/helper-validator-identifier': 7.24.5 to-fast-properties: 2.0.0 - dev: true - /@bcoe/v8-coverage@0.2.3: - resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - dev: true + '@bcoe/v8-coverage@0.2.3': {} - /@breejs/later@4.2.0: - resolution: {integrity: sha512-EVMD0SgJtOuFeg0lAVbCwa+qeTKILb87jqvLyUtQswGD9+ce2nB52Y5zbTF1Hc0MDFfbydcMcxb47jSdhikVHA==} - engines: {node: '>= 10'} - dev: false + '@breejs/later@4.2.0': {} - /@cdktf/hcl2json@0.20.3: - resolution: {integrity: sha512-GCq/GrVRXI0nR5gQM0LW7pxEA/tZav0dGQZGowHif/vXsMlOZjTh/F1ISVmDUCkNHV7pgbFmy6tDg7RtsiavXw==} + '@cdktf/hcl2json@0.20.7': dependencies: fs-extra: 11.2.0 - dev: false - /@colors/colors@1.5.0: - resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} - engines: {node: '>=0.1.90'} - requiresBuild: true - dev: true + '@colors/colors@1.5.0': optional: true - /@cspotcode/source-map-support@0.8.1: - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} + '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 - dev: true - /@eslint-community/eslint-utils@4.4.0(eslint@8.56.0): - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': dependencies: - eslint: 8.56.0 + eslint: 8.57.0 eslint-visitor-keys: 3.4.3 - dev: true - /@eslint-community/regexpp@4.10.0: - resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - dev: true + '@eslint-community/regexpp@4.10.0': {} - /@eslint/eslintrc@2.1.4: - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 debug: 4.3.4 @@ -1957,109 +7463,68 @@ packages: strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color - dev: true - - /@eslint/js@8.56.0: - resolution: {integrity: sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - /@fastify/busboy@2.1.0: - resolution: {integrity: sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==} - engines: {node: '>=14'} - dev: true + '@eslint/js@8.57.0': {} - /@gwhitney/detect-indent@7.0.1: - resolution: {integrity: sha512-7bQW+gkKa2kKZPeJf6+c6gFK9ARxQfn+FKy9ScTBppyKRWH2KzsmweXUoklqeEiHiNVWaeP5csIdsNq6w7QhzA==} - engines: {node: '>=12.20'} - dev: false + '@gwhitney/detect-indent@7.0.1': {} - /@humanwhocodes/config-array@0.11.14: - resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} - engines: {node: '>=10.10.0'} + '@humanwhocodes/config-array@0.11.14': dependencies: - '@humanwhocodes/object-schema': 2.0.2 + '@humanwhocodes/object-schema': 2.0.3 debug: 4.3.4 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - dev: true - /@humanwhocodes/module-importer@1.0.1: - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - dev: true + '@humanwhocodes/module-importer@1.0.1': {} - /@humanwhocodes/object-schema@2.0.2: - resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} - dev: true + '@humanwhocodes/object-schema@2.0.3': {} - /@hyrious/marshal@0.3.3: - resolution: {integrity: sha512-Sprz5CmX+V5MEbgOfXB0iqJS2i703RsV2cXSKC3++Y+4EeUvZPJlv0tgvoBRNT7mvb6aUu7UeOzfiowXlAOmew==} - engines: {node: ^14.18.0 || >=16.0.0} - dev: true + '@hyrious/marshal@0.3.3': {} - /@isaacs/cliui@8.0.2: - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 - string-width-cjs: /string-width@4.2.3 + string-width-cjs: string-width@4.2.3 strip-ansi: 7.1.0 - strip-ansi-cjs: /strip-ansi@6.0.1 + strip-ansi-cjs: strip-ansi@6.0.1 wrap-ansi: 8.1.0 - wrap-ansi-cjs: /wrap-ansi@7.0.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 - /@istanbuljs/load-nyc-config@1.1.0: - resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} - engines: {node: '>=8'} + '@istanbuljs/load-nyc-config@1.1.0': dependencies: camelcase: 5.3.1 find-up: 4.1.0 get-package-type: 0.1.0 js-yaml: 3.14.1 resolve-from: 5.0.0 - dev: true - /@istanbuljs/schema@0.1.3: - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} - engines: {node: '>=8'} - dev: true + '@istanbuljs/schema@0.1.3': {} - /@jest/console@29.7.0: - resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 18.19.21 + '@types/node': 18.19.33 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 slash: 3.0.0 - dev: true - /@jest/core@29.7.0(ts-node@10.9.2): - resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} - 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 + '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5))': 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': 18.19.21 + '@types/node': 18.19.33 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@18.19.21)(ts-node@10.9.2) + jest-config: 29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -2079,57 +7544,39 @@ packages: - babel-plugin-macros - supports-color - ts-node - dev: true - /@jest/environment@29.7.0: - resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/environment@29.7.0': dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.19.21 + '@types/node': 18.19.33 jest-mock: 29.7.0 - dev: true - /@jest/expect-utils@29.4.1: - resolution: {integrity: sha512-w6YJMn5DlzmxjO00i9wu2YSozUYRBhIoJ6nQwpMYcBMtiqMGJm1QBzOf6DDgRao8dbtpDoaqLg6iiQTvv0UHhQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/expect-utils@29.4.1': dependencies: jest-get-type: 29.6.3 - dev: true - /@jest/expect-utils@29.7.0: - resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/expect-utils@29.7.0': dependencies: jest-get-type: 29.6.3 - dev: true - /@jest/expect@29.7.0: - resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/expect@29.7.0': dependencies: expect: 29.7.0 jest-snapshot: 29.7.0 transitivePeerDependencies: - supports-color - dev: true - /@jest/fake-timers@29.7.0: - resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/fake-timers@29.7.0': dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 18.19.21 + '@types/node': 18.19.33 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 - dev: true - /@jest/globals@29.7.0: - resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/globals@29.7.0': dependencies: '@jest/environment': 29.7.0 '@jest/expect': 29.7.0 @@ -2137,24 +7584,16 @@ packages: jest-mock: 29.7.0 transitivePeerDependencies: - supports-color - dev: true - /@jest/reporters@29.7.0: - resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} - 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 + '@jest/reporters@29.7.0': 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.23 - '@types/node': 18.19.21 + '@jridgewell/trace-mapping': 0.3.25 + '@types/node': 18.19.33 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -2174,51 +7613,36 @@ packages: v8-to-istanbul: 9.2.0 transitivePeerDependencies: - supports-color - dev: true - /@jest/schemas@29.6.3: - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/schemas@29.6.3': dependencies: '@sinclair/typebox': 0.27.8 - dev: true - /@jest/source-map@29.6.3: - resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/source-map@29.6.3': dependencies: - '@jridgewell/trace-mapping': 0.3.23 + '@jridgewell/trace-mapping': 0.3.25 callsites: 3.1.0 graceful-fs: 4.2.11 - dev: true - /@jest/test-result@29.7.0: - resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/test-result@29.7.0': dependencies: '@jest/console': 29.7.0 '@jest/types': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 collect-v8-coverage: 1.0.2 - dev: true - /@jest/test-sequencer@29.7.0: - resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/test-sequencer@29.7.0': dependencies: '@jest/test-result': 29.7.0 graceful-fs: 4.2.11 jest-haste-map: 29.7.0 slash: 3.0.0 - dev: true - /@jest/transform@29.7.0: - resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/transform@29.7.0': dependencies: - '@babel/core': 7.23.9 + '@babel/core': 7.24.5 '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.23 + '@jridgewell/trace-mapping': 0.3.25 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 convert-source-map: 2.0.0 @@ -2233,500 +7657,329 @@ packages: write-file-atomic: 4.0.2 transitivePeerDependencies: - supports-color - dev: true - /@jest/types@29.6.3: - resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/types@29.6.3': dependencies: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 18.19.21 + '@types/node': 18.19.33 '@types/yargs': 17.0.32 chalk: 4.1.2 - dev: true - /@jridgewell/gen-mapping@0.3.4: - resolution: {integrity: sha512-Oud2QPM5dHviZNn4y/WhhYKSXksv+1xLEIsNrAbGcFzUN3ubqWRFT5gwPchNc5NuzILOU4tPBDTZ4VwhL8Y7cw==} - engines: {node: '>=6.0.0'} + '@jridgewell/gen-mapping@0.3.5': dependencies: - '@jridgewell/set-array': 1.1.2 + '@jridgewell/set-array': 1.2.1 '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.23 - dev: true + '@jridgewell/trace-mapping': 0.3.25 - /@jridgewell/resolve-uri@3.1.2: - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - dev: true + '@jridgewell/resolve-uri@3.1.2': {} - /@jridgewell/set-array@1.1.2: - resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} - engines: {node: '>=6.0.0'} - dev: true + '@jridgewell/set-array@1.2.1': {} - /@jridgewell/sourcemap-codec@1.4.15: - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - dev: true + '@jridgewell/sourcemap-codec@1.4.15': {} - /@jridgewell/trace-mapping@0.3.23: - resolution: {integrity: sha512-9/4foRoUKp8s96tSkh8DlAAc5A0Ty8vLXld+l9gjKKY6ckwI8G15f0hskGmuLZu78ZlGa1vtsfOa+lnB4vG6Jg==} + '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 - dev: true - /@jridgewell/trace-mapping@0.3.9: - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@jridgewell/trace-mapping@0.3.9': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 - dev: true - /@kwsites/file-exists@1.1.1: - resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} + '@jsonjoy.com/base64@1.1.1(tslib@2.6.2)': + dependencies: + tslib: 2.6.2 + + '@jsonjoy.com/json-pack@1.0.3(tslib@2.6.2)': + dependencies: + '@jsonjoy.com/base64': 1.1.1(tslib@2.6.2) + '@jsonjoy.com/util': 1.1.2(tslib@2.6.2) + hyperdyperid: 1.2.0 + thingies: 1.21.0(tslib@2.6.2) + tslib: 2.6.2 + + '@jsonjoy.com/util@1.1.2(tslib@2.6.2)': + dependencies: + tslib: 2.6.2 + + '@kwsites/file-exists@1.1.1': dependencies: debug: 4.3.4 transitivePeerDependencies: - supports-color - dev: false - /@kwsites/promise-deferred@1.1.1: - resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} - dev: false + '@kwsites/promise-deferred@1.1.1': {} - /@ls-lint/ls-lint@2.2.2: - resolution: {integrity: sha512-gdolCgwveSHZczfKtoVFkf+NZJjd7KRQs9QnLVEiMFEJLK4H7nBrZuC/ASl/AYMZvFmtCocRt3d5E0UF/34vCQ==} - cpu: [x64, arm64, s390x] - os: [darwin, linux, win32] - hasBin: true - dev: true + '@ls-lint/ls-lint@2.2.3': {} - /@nodelib/fs.scandir@2.1.5: - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 - /@nodelib/fs.stat@2.0.5: - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} + '@nodelib/fs.stat@2.0.5': {} - /@nodelib/fs.walk@1.2.8: - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} + '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 - /@npmcli/agent@2.2.1: - resolution: {integrity: sha512-H4FrOVtNyWC8MUwL3UfjOsAihHvT1Pe8POj3JvjXhSTJipsZMtgUALCT4mGyYZNxymkUfOw3PUj6dE4QPp6osQ==} - engines: {node: ^16.14.0 || >=18.0.0} - requiresBuild: true + '@npmcli/agent@2.2.2': dependencies: - agent-base: 7.1.0 + agent-base: 7.1.1 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.4 - lru-cache: 10.2.0 - socks-proxy-agent: 8.0.2 + lru-cache: 10.2.2 + socks-proxy-agent: 8.0.3 transitivePeerDependencies: - supports-color - dev: false optional: true - /@npmcli/fs@3.1.0: - resolution: {integrity: sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + '@npmcli/fs@3.1.0': dependencies: - semver: 7.5.4 - dev: false + semver: 7.6.2 - /@octokit/auth-token@4.0.0: - resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==} - engines: {node: '>= 18'} + '@octokit/auth-token@4.0.0': {} - /@octokit/core@5.1.0: - resolution: {integrity: sha512-BDa2VAMLSh3otEiaMJ/3Y36GU4qf6GI+VivQ/P41NC6GHcdxpKlqV0ikSZ5gdQsmS3ojXeRx5vasgNTinF0Q4g==} - engines: {node: '>= 18'} + '@octokit/core@5.2.0': dependencies: '@octokit/auth-token': 4.0.0 - '@octokit/graphql': 7.0.2 - '@octokit/request': 8.2.0 - '@octokit/request-error': 5.0.1 - '@octokit/types': 12.6.0 + '@octokit/graphql': 7.1.0 + '@octokit/request': 8.4.0 + '@octokit/request-error': 5.1.0 + '@octokit/types': 13.5.0 before-after-hook: 2.2.3 universal-user-agent: 6.0.1 - /@octokit/endpoint@9.0.4: - resolution: {integrity: sha512-DWPLtr1Kz3tv8L0UvXTDP1fNwM0S+z6EJpRcvH66orY6Eld4XBMCSYsaWp4xIm61jTWxK68BrR7ibO+vSDnZqw==} - engines: {node: '>= 18'} + '@octokit/endpoint@9.0.5': dependencies: - '@octokit/types': 12.6.0 + '@octokit/types': 13.5.0 universal-user-agent: 6.0.1 - /@octokit/graphql@7.0.2: - resolution: {integrity: sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==} - engines: {node: '>= 18'} + '@octokit/graphql@7.1.0': dependencies: - '@octokit/request': 8.2.0 - '@octokit/types': 12.6.0 + '@octokit/request': 8.4.0 + '@octokit/types': 13.5.0 universal-user-agent: 6.0.1 - /@octokit/openapi-types@20.0.0: - resolution: {integrity: sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==} + '@octokit/openapi-types@20.0.0': {} - /@octokit/plugin-paginate-rest@9.2.0(@octokit/core@5.1.0): - resolution: {integrity: sha512-NKi0bJEZqOSbBLMv9kdAcuocpe05Q2xAXNLTGi0HN2GSMFJHNZuSoPNa0tcQFTOFCKe+ZaYBZ3lpXh1yxgUDCA==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': '>=5' + '@octokit/openapi-types@22.2.0': {} + + '@octokit/plugin-paginate-rest@11.3.1(@octokit/core@5.2.0)': + dependencies: + '@octokit/core': 5.2.0 + '@octokit/types': 13.5.0 + + '@octokit/plugin-paginate-rest@9.2.1(@octokit/core@5.2.0)': dependencies: - '@octokit/core': 5.1.0 + '@octokit/core': 5.2.0 '@octokit/types': 12.6.0 - /@octokit/plugin-request-log@4.0.0(@octokit/core@5.1.0): - resolution: {integrity: sha512-2uJI1COtYCq8Z4yNSnM231TgH50bRkheQ9+aH8TnZanB6QilOnx8RMD2qsnamSOXtDj0ilxvevf5fGsBhBBzKA==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': '>=5' + '@octokit/plugin-request-log@4.0.1(@octokit/core@5.2.0)': dependencies: - '@octokit/core': 5.1.0 - dev: false + '@octokit/core': 5.2.0 - /@octokit/plugin-rest-endpoint-methods@10.4.0(@octokit/core@5.1.0): - resolution: {integrity: sha512-INw5rGXWlbv/p/VvQL63dhlXr38qYTHkQ5bANi9xofrF9OraqmjHsIGyenmjmul1JVRHpUlw5heFOj1UZLEolA==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': '>=5' + '@octokit/plugin-rest-endpoint-methods@13.2.2(@octokit/core@5.2.0)': dependencies: - '@octokit/core': 5.1.0 - '@octokit/types': 12.6.0 - dev: false + '@octokit/core': 5.2.0 + '@octokit/types': 13.5.0 - /@octokit/plugin-retry@6.0.1(@octokit/core@5.1.0): - resolution: {integrity: sha512-SKs+Tz9oj0g4p28qkZwl/topGcb0k0qPNX/i7vBKmDsjoeqnVfFUquqrE/O9oJY7+oLzdCtkiWSXLpLjvl6uog==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': '>=5' + '@octokit/plugin-retry@6.0.1(@octokit/core@5.2.0)': dependencies: - '@octokit/core': 5.1.0 - '@octokit/request-error': 5.0.1 + '@octokit/core': 5.2.0 + '@octokit/request-error': 5.1.0 '@octokit/types': 12.6.0 bottleneck: 2.19.5 - dev: true - /@octokit/plugin-throttling@8.2.0(@octokit/core@5.1.0): - resolution: {integrity: sha512-nOpWtLayKFpgqmgD0y3GqXafMFuKcA4tRPZIfu7BArd2lEZeb1988nhWhwx4aZWmjDmUfdgVf7W+Tt4AmvRmMQ==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': ^5.0.0 + '@octokit/plugin-throttling@8.2.0(@octokit/core@5.2.0)': dependencies: - '@octokit/core': 5.1.0 + '@octokit/core': 5.2.0 '@octokit/types': 12.6.0 bottleneck: 2.19.5 - dev: true - /@octokit/request-error@5.0.1: - resolution: {integrity: sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==} - engines: {node: '>= 18'} + '@octokit/request-error@5.1.0': dependencies: - '@octokit/types': 12.6.0 + '@octokit/types': 13.5.0 deprecation: 2.3.1 once: 1.4.0 - /@octokit/request@8.2.0: - resolution: {integrity: sha512-exPif6x5uwLqv1N1irkLG1zZNJkOtj8bZxuVHd71U5Ftuxf2wGNvAJyNBcPbPC+EBzwYEbBDdSFb8EPcjpYxPQ==} - engines: {node: '>= 18'} + '@octokit/request@8.4.0': dependencies: - '@octokit/endpoint': 9.0.4 - '@octokit/request-error': 5.0.1 - '@octokit/types': 12.6.0 + '@octokit/endpoint': 9.0.5 + '@octokit/request-error': 5.1.0 + '@octokit/types': 13.5.0 universal-user-agent: 6.0.1 - /@octokit/rest@20.0.2: - resolution: {integrity: sha512-Ux8NDgEraQ/DMAU1PlAohyfBBXDwhnX2j33Z1nJNziqAfHi70PuxkFYIcIt8aIAxtRE7KVuKp8lSR8pA0J5iOQ==} - engines: {node: '>= 18'} + '@octokit/rest@20.1.1': dependencies: - '@octokit/core': 5.1.0 - '@octokit/plugin-paginate-rest': 9.2.0(@octokit/core@5.1.0) - '@octokit/plugin-request-log': 4.0.0(@octokit/core@5.1.0) - '@octokit/plugin-rest-endpoint-methods': 10.4.0(@octokit/core@5.1.0) - dev: false + '@octokit/core': 5.2.0 + '@octokit/plugin-paginate-rest': 11.3.1(@octokit/core@5.2.0) + '@octokit/plugin-request-log': 4.0.1(@octokit/core@5.2.0) + '@octokit/plugin-rest-endpoint-methods': 13.2.2(@octokit/core@5.2.0) - /@octokit/types@12.6.0: - resolution: {integrity: sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==} + '@octokit/types@12.6.0': dependencies: '@octokit/openapi-types': 20.0.0 - /@one-ini/wasm@0.1.1: - resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} - dev: false - - /@openpgp/web-stream-tools@0.0.14(typescript@5.3.3): - resolution: {integrity: sha512-6btCNVf6YSsmlyIS7yw+IbzXeXCEcJxeSpxvSxkDuZj9B/ekt4fXkZj4oOaIxG4SKTftIK1svnlVroJ1cCMT4g==} - peerDependencies: - typescript: '>=4.2' - peerDependenciesMeta: - typescript: - optional: true + '@octokit/types@13.5.0': dependencies: - typescript: 5.3.3 - dev: true + '@octokit/openapi-types': 22.2.0 - /@opentelemetry/api-logs@0.48.0: - resolution: {integrity: sha512-1/aMiU4Eqo3Zzpfwu51uXssp5pzvHFObk8S9pKAiXb1ne8pvg1qxBQitYL1XUiAMEXFzgjaidYG2V6624DRhhw==} - engines: {node: '>=14'} + '@one-ini/wasm@0.1.1': {} + + '@openpgp/web-stream-tools@0.0.14(typescript@5.4.5)': + optionalDependencies: + typescript: 5.4.5 + + '@opentelemetry/api-logs@0.51.1': dependencies: - '@opentelemetry/api': 1.7.0 - dev: false + '@opentelemetry/api': 1.8.0 - /@opentelemetry/api@1.7.0: - resolution: {integrity: sha512-AdY5wvN0P2vXBi3b29hxZgSFvdhdxPB9+f0B6s//P9Q8nibRWeA3cHm8UmLpio9ABigkVHJ5NMPk+Mz8VCCyrw==} - engines: {node: '>=8.0.0'} - dev: false + '@opentelemetry/api@1.8.0': {} - /@opentelemetry/context-async-hooks@1.21.0(@opentelemetry/api@1.7.0): - resolution: {integrity: sha512-t0iulGPiMjG/NrSjinPQoIf8ST/o9V0dGOJthfrFporJlNdlKIQPfC7lkrV+5s2dyBThfmSbJlp/4hO1eOcDXA==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.8.0' + '@opentelemetry/context-async-hooks@1.24.1(@opentelemetry/api@1.8.0)': dependencies: - '@opentelemetry/api': 1.7.0 - dev: false + '@opentelemetry/api': 1.8.0 - /@opentelemetry/core@1.21.0(@opentelemetry/api@1.7.0): - resolution: {integrity: sha512-KP+OIweb3wYoP7qTYL/j5IpOlu52uxBv5M4+QhSmmUfLyTgu1OIS71msK3chFo1D6Y61BIH3wMiMYRCxJCQctA==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.8.0' + '@opentelemetry/core@1.24.1(@opentelemetry/api@1.8.0)': dependencies: - '@opentelemetry/api': 1.7.0 - '@opentelemetry/semantic-conventions': 1.21.0 - dev: false + '@opentelemetry/api': 1.8.0 + '@opentelemetry/semantic-conventions': 1.24.1 - /@opentelemetry/exporter-trace-otlp-http@0.48.0(@opentelemetry/api@1.7.0): - resolution: {integrity: sha512-QEZKbfWqXrbKVpr2PHd4KyKI0XVOhUYC+p2RPV8s+2K5QzZBE3+F9WlxxrXDfkrvGmpQAZytBoHQQYA3AGOtpw==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.0.0 + '@opentelemetry/exporter-trace-otlp-http@0.51.1(@opentelemetry/api@1.8.0)': dependencies: - '@opentelemetry/api': 1.7.0 - '@opentelemetry/core': 1.21.0(@opentelemetry/api@1.7.0) - '@opentelemetry/otlp-exporter-base': 0.48.0(@opentelemetry/api@1.7.0) - '@opentelemetry/otlp-transformer': 0.48.0(@opentelemetry/api@1.7.0) - '@opentelemetry/resources': 1.21.0(@opentelemetry/api@1.7.0) - '@opentelemetry/sdk-trace-base': 1.21.0(@opentelemetry/api@1.7.0) - dev: false + '@opentelemetry/api': 1.8.0 + '@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0) + '@opentelemetry/otlp-exporter-base': 0.51.1(@opentelemetry/api@1.8.0) + '@opentelemetry/otlp-transformer': 0.51.1(@opentelemetry/api@1.8.0) + '@opentelemetry/resources': 1.24.1(@opentelemetry/api@1.8.0) + '@opentelemetry/sdk-trace-base': 1.24.1(@opentelemetry/api@1.8.0) - /@opentelemetry/instrumentation-bunyan@0.35.0(@opentelemetry/api@1.7.0): - resolution: {integrity: sha512-bQ8OzV7nVTA+oGiTzLjUmRFAbnXi0U/Z4VJCpj+1DRsaAaMT17eRpAOh22LQR0JBnv2vBm8CvIQl4CcAnsB46g==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-bunyan@0.38.0(@opentelemetry/api@1.8.0)': dependencies: - '@opentelemetry/api': 1.7.0 - '@opentelemetry/api-logs': 0.48.0 - '@opentelemetry/instrumentation': 0.48.0(@opentelemetry/api@1.7.0) + '@opentelemetry/api': 1.8.0 + '@opentelemetry/api-logs': 0.51.1 + '@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.8.0) '@types/bunyan': 1.8.9 transitivePeerDependencies: - supports-color - dev: false - /@opentelemetry/instrumentation-http@0.48.0(@opentelemetry/api@1.7.0): - resolution: {integrity: sha512-uXqOsLhW9WC3ZlGm6+PSX0xjSDTCfy4CMjfYj6TPWusOO8dtdx040trOriF24y+sZmS3M+5UQc6/3/ZxBJh4Mw==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-http@0.51.1(@opentelemetry/api@1.8.0)': dependencies: - '@opentelemetry/api': 1.7.0 - '@opentelemetry/core': 1.21.0(@opentelemetry/api@1.7.0) - '@opentelemetry/instrumentation': 0.48.0(@opentelemetry/api@1.7.0) - '@opentelemetry/semantic-conventions': 1.21.0 - semver: 7.5.4 + '@opentelemetry/api': 1.8.0 + '@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0) + '@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.8.0) + '@opentelemetry/semantic-conventions': 1.24.1 + semver: 7.6.2 transitivePeerDependencies: - supports-color - dev: false - /@opentelemetry/instrumentation@0.48.0(@opentelemetry/api@1.7.0): - resolution: {integrity: sha512-sjtZQB5PStIdCw5ovVTDGwnmQC+GGYArJNgIcydrDSqUTdYBnMrN9P4pwQZgS3vTGIp+TU1L8vMXGe51NVmIKQ==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation@0.51.1(@opentelemetry/api@1.8.0)': dependencies: - '@opentelemetry/api': 1.7.0 + '@opentelemetry/api': 1.8.0 + '@opentelemetry/api-logs': 0.51.1 '@types/shimmer': 1.0.5 - import-in-the-middle: 1.7.1 - require-in-the-middle: 7.2.0 - semver: 7.5.4 + import-in-the-middle: 1.7.4 + require-in-the-middle: 7.3.0 + semver: 7.6.2 shimmer: 1.2.1 transitivePeerDependencies: - supports-color - dev: false - /@opentelemetry/otlp-exporter-base@0.48.0(@opentelemetry/api@1.7.0): - resolution: {integrity: sha512-T4LJND+Ugl87GUONoyoQzuV9qCn4BFIPOnCH1biYqdGhc2JahjuLqVD9aefwLzGBW638iLAo88Lh68h2F1FLiA==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.0.0 + '@opentelemetry/otlp-exporter-base@0.51.1(@opentelemetry/api@1.8.0)': dependencies: - '@opentelemetry/api': 1.7.0 - '@opentelemetry/core': 1.21.0(@opentelemetry/api@1.7.0) - dev: false + '@opentelemetry/api': 1.8.0 + '@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0) - /@opentelemetry/otlp-transformer@0.48.0(@opentelemetry/api@1.7.0): - resolution: {integrity: sha512-yuoS4cUumaTK/hhxW3JUy3wl2U4keMo01cFDrUOmjloAdSSXvv1zyQ920IIH4lymp5Xd21Dj2/jq2LOro56TJg==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': '>=1.3.0 <1.8.0' - dependencies: - '@opentelemetry/api': 1.7.0 - '@opentelemetry/api-logs': 0.48.0 - '@opentelemetry/core': 1.21.0(@opentelemetry/api@1.7.0) - '@opentelemetry/resources': 1.21.0(@opentelemetry/api@1.7.0) - '@opentelemetry/sdk-logs': 0.48.0(@opentelemetry/api-logs@0.48.0)(@opentelemetry/api@1.7.0) - '@opentelemetry/sdk-metrics': 1.21.0(@opentelemetry/api@1.7.0) - '@opentelemetry/sdk-trace-base': 1.21.0(@opentelemetry/api@1.7.0) - dev: false - - /@opentelemetry/propagator-b3@1.21.0(@opentelemetry/api@1.7.0): - resolution: {integrity: sha512-3ZTobj2VDIOzLsIvvYCdpw6tunxUVElPxDvog9lS49YX4hohHeD84A8u9Ns/6UYUcaN5GSoEf891lzhcBFiOLA==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.8.0' + '@opentelemetry/otlp-transformer@0.51.1(@opentelemetry/api@1.8.0)': dependencies: - '@opentelemetry/api': 1.7.0 - '@opentelemetry/core': 1.21.0(@opentelemetry/api@1.7.0) - dev: false + '@opentelemetry/api': 1.8.0 + '@opentelemetry/api-logs': 0.51.1 + '@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0) + '@opentelemetry/resources': 1.24.1(@opentelemetry/api@1.8.0) + '@opentelemetry/sdk-logs': 0.51.1(@opentelemetry/api-logs@0.51.1)(@opentelemetry/api@1.8.0) + '@opentelemetry/sdk-metrics': 1.24.1(@opentelemetry/api@1.8.0) + '@opentelemetry/sdk-trace-base': 1.24.1(@opentelemetry/api@1.8.0) - /@opentelemetry/propagator-jaeger@1.21.0(@opentelemetry/api@1.7.0): - resolution: {integrity: sha512-8TQSwXjBmaDx7JkxRD7hdmBmRK2RGRgzHX1ArJfJhIc5trzlVweyorzqQrXOvqVEdEg+zxUMHkL5qbGH/HDTPA==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.8.0' + '@opentelemetry/propagator-b3@1.24.1(@opentelemetry/api@1.8.0)': dependencies: - '@opentelemetry/api': 1.7.0 - '@opentelemetry/core': 1.21.0(@opentelemetry/api@1.7.0) - dev: false + '@opentelemetry/api': 1.8.0 + '@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0) - /@opentelemetry/resources@1.21.0(@opentelemetry/api@1.7.0): - resolution: {integrity: sha512-1Z86FUxPKL6zWVy2LdhueEGl9AHDJcx+bvHStxomruz6Whd02mE3lNUMjVJ+FGRoktx/xYQcxccYb03DiUP6Yw==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.8.0' + '@opentelemetry/propagator-jaeger@1.24.1(@opentelemetry/api@1.8.0)': dependencies: - '@opentelemetry/api': 1.7.0 - '@opentelemetry/core': 1.21.0(@opentelemetry/api@1.7.0) - '@opentelemetry/semantic-conventions': 1.21.0 - dev: false + '@opentelemetry/api': 1.8.0 + '@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0) - /@opentelemetry/sdk-logs@0.48.0(@opentelemetry/api-logs@0.48.0)(@opentelemetry/api@1.7.0): - resolution: {integrity: sha512-lRcA5/qkSJuSh4ItWCddhdn/nNbVvnzM+cm9Fg1xpZUeTeozjJDBcHnmeKoOaWRnrGYBdz6UTY6bynZR9aBeAA==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': '>=1.4.0 <1.8.0' - '@opentelemetry/api-logs': '>=0.39.1' + '@opentelemetry/resources@1.24.1(@opentelemetry/api@1.8.0)': dependencies: - '@opentelemetry/api': 1.7.0 - '@opentelemetry/api-logs': 0.48.0 - '@opentelemetry/core': 1.21.0(@opentelemetry/api@1.7.0) - '@opentelemetry/resources': 1.21.0(@opentelemetry/api@1.7.0) - dev: false + '@opentelemetry/api': 1.8.0 + '@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0) + '@opentelemetry/semantic-conventions': 1.24.1 - /@opentelemetry/sdk-metrics@1.21.0(@opentelemetry/api@1.7.0): - resolution: {integrity: sha512-on1jTzIHc5DyWhRP+xpf+zrgrREXcHBH4EDAfaB5mIG7TWpKxNXooQ1JCylaPsswZUv4wGnVTinr4HrBdGARAQ==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': '>=1.3.0 <1.8.0' + '@opentelemetry/sdk-logs@0.51.1(@opentelemetry/api-logs@0.51.1)(@opentelemetry/api@1.8.0)': + dependencies: + '@opentelemetry/api': 1.8.0 + '@opentelemetry/api-logs': 0.51.1 + '@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0) + '@opentelemetry/resources': 1.24.1(@opentelemetry/api@1.8.0) + + '@opentelemetry/sdk-metrics@1.24.1(@opentelemetry/api@1.8.0)': dependencies: - '@opentelemetry/api': 1.7.0 - '@opentelemetry/core': 1.21.0(@opentelemetry/api@1.7.0) - '@opentelemetry/resources': 1.21.0(@opentelemetry/api@1.7.0) + '@opentelemetry/api': 1.8.0 + '@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0) + '@opentelemetry/resources': 1.24.1(@opentelemetry/api@1.8.0) lodash.merge: 4.6.2 - dev: false - /@opentelemetry/sdk-trace-base@1.21.0(@opentelemetry/api@1.7.0): - resolution: {integrity: sha512-yrElGX5Fv0umzp8Nxpta/XqU71+jCAyaLk34GmBzNcrW43nqbrqvdPs4gj4MVy/HcTjr6hifCDCYA3rMkajxxA==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.8.0' + '@opentelemetry/sdk-trace-base@1.24.1(@opentelemetry/api@1.8.0)': dependencies: - '@opentelemetry/api': 1.7.0 - '@opentelemetry/core': 1.21.0(@opentelemetry/api@1.7.0) - '@opentelemetry/resources': 1.21.0(@opentelemetry/api@1.7.0) - '@opentelemetry/semantic-conventions': 1.21.0 - dev: false + '@opentelemetry/api': 1.8.0 + '@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0) + '@opentelemetry/resources': 1.24.1(@opentelemetry/api@1.8.0) + '@opentelemetry/semantic-conventions': 1.24.1 - /@opentelemetry/sdk-trace-node@1.21.0(@opentelemetry/api@1.7.0): - resolution: {integrity: sha512-1pdm8jnqs+LuJ0Bvx6sNL28EhC8Rv7NYV8rnoXq3GIQo7uOHBDAFSj7makAfbakrla7ecO1FRfI8emnR4WvhYA==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.8.0' - dependencies: - '@opentelemetry/api': 1.7.0 - '@opentelemetry/context-async-hooks': 1.21.0(@opentelemetry/api@1.7.0) - '@opentelemetry/core': 1.21.0(@opentelemetry/api@1.7.0) - '@opentelemetry/propagator-b3': 1.21.0(@opentelemetry/api@1.7.0) - '@opentelemetry/propagator-jaeger': 1.21.0(@opentelemetry/api@1.7.0) - '@opentelemetry/sdk-trace-base': 1.21.0(@opentelemetry/api@1.7.0) - semver: 7.5.4 - dev: false - - /@opentelemetry/semantic-conventions@1.21.0: - resolution: {integrity: sha512-lkC8kZYntxVKr7b8xmjCVUgE0a8xgDakPyDo9uSWavXPyYqLgYYGdEd2j8NxihRyb6UwpX3G/hFUF4/9q2V+/g==} - engines: {node: '>=14'} - dev: false + '@opentelemetry/sdk-trace-node@1.24.1(@opentelemetry/api@1.8.0)': + dependencies: + '@opentelemetry/api': 1.8.0 + '@opentelemetry/context-async-hooks': 1.24.1(@opentelemetry/api@1.8.0) + '@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0) + '@opentelemetry/propagator-b3': 1.24.1(@opentelemetry/api@1.8.0) + '@opentelemetry/propagator-jaeger': 1.24.1(@opentelemetry/api@1.8.0) + '@opentelemetry/sdk-trace-base': 1.24.1(@opentelemetry/api@1.8.0) + semver: 7.6.2 - /@pkgjs/parseargs@0.11.0: - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - requiresBuild: true + '@opentelemetry/semantic-conventions@1.24.1': {} + + '@pkgjs/parseargs@0.11.0': optional: true - /@pnpm/config.env-replace@1.1.0: - resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} - engines: {node: '>=12.22.0'} - dev: true + '@pnpm/config.env-replace@1.1.0': {} - /@pnpm/constants@6.1.0: - resolution: {integrity: sha512-L6AiU3OXv9kjKGTJN9j8n1TeJGDcLX9atQlZvAkthlvbXjvKc5SKNWESc/eXhr5nEfuMWhQhiKHDJCpYejmeCQ==} - engines: {node: '>=14.19'} - dev: false + '@pnpm/constants@6.1.0': {} - /@pnpm/error@4.0.0: - resolution: {integrity: sha512-NI4DFCMF6xb1SA0bZiiV5KrMCaJM2QmPJFC6p78FXujn7FpiRSWhT9r032wpuQumsl7DEmN4s3wl/P8TA+bL8w==} - engines: {node: '>=14.6'} + '@pnpm/error@4.0.0': dependencies: '@pnpm/constants': 6.1.0 - dev: false - /@pnpm/graceful-fs@2.0.0: - resolution: {integrity: sha512-ogUZCGf0/UILZt6d8PsO4gA4pXh7f0BumXeFkcCe4AQ65PXPKfAkHC0C30Lheh2EgFOpLZm3twDP1Eiww18gew==} - engines: {node: '>=14.19'} + '@pnpm/graceful-fs@2.0.0': dependencies: graceful-fs: 4.2.11 - dev: false - /@pnpm/network.ca-file@1.0.2: - resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} - engines: {node: '>=12.22.0'} + '@pnpm/network.ca-file@1.0.2': dependencies: graceful-fs: 4.2.10 - dev: true - /@pnpm/npm-conf@2.2.2: - resolution: {integrity: sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==} - engines: {node: '>=12'} + '@pnpm/npm-conf@2.2.2': dependencies: '@pnpm/config.env-replace': 1.1.0 '@pnpm/network.ca-file': 1.0.2 config-chain: 1.1.13 - dev: true - /@pnpm/read-project-manifest@4.1.1: - resolution: {integrity: sha512-jGNoofG8kkUlgAMX8fqbUwRRXYf4WcWdvi/y1Sv1abUfcoVgXW6GdGVm0MIJ+enaong3hXHjaLl/AwmSj6O1Uw==} - engines: {node: '>=14.6'} + '@pnpm/read-project-manifest@4.1.1': dependencies: '@gwhitney/detect-indent': 7.0.1 '@pnpm/error': 4.0.0 @@ -2741,166 +7994,117 @@ packages: read-yaml-file: 2.1.0 sort-keys: 4.2.0 strip-bom: 4.0.0 - dev: false - /@pnpm/text.comments-parser@1.0.0: - resolution: {integrity: sha512-iG0qrFcObze3uK+HligvzaTocZKukqqIj1dC3NOH58NeMACUW1NUitSKBgeWuNIE4LJT3SPxnyLEBARMMcqVKA==} - engines: {node: '>=14.6'} + '@pnpm/text.comments-parser@1.0.0': dependencies: strip-comments-strings: 1.2.0 - dev: false - /@pnpm/types@8.9.0: - resolution: {integrity: sha512-3MYHYm8epnciApn6w5Fzx6sepawmsNU7l6lvIq+ER22/DPSrr83YMhU/EQWnf4lORn2YyiXFj0FJSyJzEtIGmw==} - engines: {node: '>=14.6'} - dev: false + '@pnpm/types@8.9.0': {} - /@pnpm/util.lex-comparator@1.0.0: - resolution: {integrity: sha512-3aBQPHntVgk5AweBWZn+1I/fqZ9krK/w01197aYVkAJQGftb+BVWgEepxY5GChjSW12j52XX+CmfynYZ/p0DFQ==} - engines: {node: '>=12.22.0'} - dev: false + '@pnpm/util.lex-comparator@1.0.0': {} - /@pnpm/write-project-manifest@4.1.1: - resolution: {integrity: sha512-nRqvPYO8xUVdgy/KhJuaCrWlVT/4uZr97Mpbuizsa6CmvtCQf3NuYnVvOOrpYiKUJcZYtEvm84OooJ8+lJytMQ==} - engines: {node: '>=14.6'} + '@pnpm/write-project-manifest@4.1.1': dependencies: '@pnpm/text.comments-parser': 1.0.0 '@pnpm/types': 8.9.0 json5: 2.2.3 write-file-atomic: 5.0.1 write-yaml-file: 4.2.0 - dev: false - /@qnighy/marshal@0.1.3: - resolution: {integrity: sha512-uaDZTJYtD2UgQTGemmgWeth+e2WapZm+GkAq8UU8AJ55PKRFaf1GkH7X/uzA+Ygu8iInzIlM2FGyCUnruyMKMg==} + '@qnighy/marshal@0.1.3': dependencies: - '@babel/runtime-corejs3': 7.23.9 - dev: false + '@babel/runtime-corejs3': 7.24.5 - /@redis/bloom@1.2.0(@redis/client@1.5.14): - resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==} - peerDependencies: - '@redis/client': ^1.0.0 + '@redis/bloom@1.2.0(@redis/client@1.5.14)': dependencies: '@redis/client': 1.5.14 - dev: false - /@redis/client@1.5.14: - resolution: {integrity: sha512-YGn0GqsRBFUQxklhY7v562VMOP0DcmlrHHs3IV1mFE3cbxe31IITUkqhBcIhVSI/2JqtWAJXg5mjV4aU+zD0HA==} - engines: {node: '>=14'} + '@redis/client@1.5.14': dependencies: cluster-key-slot: 1.1.2 generic-pool: 3.9.0 yallist: 4.0.0 - dev: false - /@redis/graph@1.1.1(@redis/client@1.5.14): - resolution: {integrity: sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==} - peerDependencies: - '@redis/client': ^1.0.0 + '@redis/graph@1.1.1(@redis/client@1.5.14)': dependencies: '@redis/client': 1.5.14 - dev: false - /@redis/json@1.0.6(@redis/client@1.5.14): - resolution: {integrity: sha512-rcZO3bfQbm2zPRpqo82XbW8zg4G/w4W3tI7X8Mqleq9goQjAGLL7q/1n1ZX4dXEAmORVZ4s1+uKLaUOg7LrUhw==} - peerDependencies: - '@redis/client': ^1.0.0 + '@redis/json@1.0.6(@redis/client@1.5.14)': dependencies: '@redis/client': 1.5.14 - dev: false - /@redis/search@1.1.6(@redis/client@1.5.14): - resolution: {integrity: sha512-mZXCxbTYKBQ3M2lZnEddwEAks0Kc7nauire8q20oA0oA/LoA+E/b5Y5KZn232ztPb1FkIGqo12vh3Lf+Vw5iTw==} - peerDependencies: - '@redis/client': ^1.0.0 + '@redis/search@1.1.6(@redis/client@1.5.14)': dependencies: '@redis/client': 1.5.14 - dev: false - /@redis/time-series@1.0.5(@redis/client@1.5.14): - resolution: {integrity: sha512-IFjIgTusQym2B5IZJG3XKr5llka7ey84fw/NOYqESP5WUfQs9zz1ww/9+qoz4ka/S6KcGBodzlCeZ5UImKbscg==} - peerDependencies: - '@redis/client': ^1.0.0 + '@redis/time-series@1.0.5(@redis/client@1.5.14)': dependencies: '@redis/client': 1.5.14 - dev: false - /@renovatebot/osv-offline-db@1.6.0: - resolution: {integrity: sha512-cEOCTyd3+/7gPDmBn0pyJtF01+f9e/dJ1mOoML+v5AsP8GIPAzhtQUuIB5FiCxS4IsbP0qm34anYUZHGJldNJA==} + '@renovatebot/eslint-plugin@file:tools/eslint': {} + + '@renovatebot/kbpgp@3.0.1': + dependencies: + bn: 1.0.5 + bzip-deflate: 1.0.0 + deep-equal: 2.2.3 + iced-error: 0.0.13 + iced-lock: 2.0.1 + iced-runtime-3: 3.0.5 + keybase-ecurve: 1.0.1 + keybase-nacl: 1.1.4 + minimist: 1.2.8 + pgp-utils: 0.0.35 + purepack: 1.0.6 + triplesec: 4.0.3 + tweetnacl: 1.0.3 + + '@renovatebot/osv-offline-db@1.6.0': dependencies: '@seald-io/nedb': 4.0.4 - dev: false - /@renovatebot/osv-offline@1.5.1: - resolution: {integrity: sha512-NQT7ikG+uA2M9IrGEY/f43FNXWQrFbIKSKk4kFxcOJL+Y3mYJF6EpVcqxfp2ExIrwu50r2rBbzdX9jgbuhiwwQ==} + '@renovatebot/osv-offline@1.5.5(encoding@0.1.13)': dependencies: - '@octokit/rest': 20.0.2 + '@octokit/rest': 20.1.1 '@renovatebot/osv-offline-db': 1.6.0 - adm-zip: 0.5.10 + adm-zip: 0.5.12 fs-extra: 11.2.0 got: 11.8.6 luxon: 3.4.4 - node-fetch: 2.7.0 + node-fetch: 2.7.0(encoding@0.1.13) transitivePeerDependencies: - encoding - dev: false - /@renovatebot/pep440@3.0.19: - resolution: {integrity: sha512-Y5/rKFwQUPZJOEMmCLqQ1TFJ1v/lN88wGLPzcwKxzgiBWZUVZD47sppV9aObguSmeOni1uEg3wz0I/jqHSP+XA==} - engines: {node: ^18.12.0 || >= 20.0.0, pnpm: ^8.6.11} - dev: false + '@renovatebot/pep440@3.0.20': {} - /@renovatebot/ruby-semver@3.0.23: - resolution: {integrity: sha512-YGvsvvyxOgv5Uq+sFEdD1yviyrPGs9hocjhIo7uWTj/EAIlbGyk5YA5JrHql3EkJf0tVsyfmEkM3kLK+45hmIw==} - engines: {node: ^18.12.0 || >= 20.0.0, pnpm: ^8.6.11} - dev: false + '@renovatebot/ruby-semver@3.0.23': {} - /@seald-io/binary-search-tree@1.0.3: - resolution: {integrity: sha512-qv3jnwoakeax2razYaMsGI/luWdliBLHTdC6jU55hQt1hcFqzauH/HsBollQ7IR4ySTtYhT+xyHoijpA16C+tA==} - dev: false + '@seald-io/binary-search-tree@1.0.3': {} - /@seald-io/nedb@4.0.4: - resolution: {integrity: sha512-CUNcMio7QUHTA+sIJ/DC5JzVNNsHe743TPmC4H5Gij9zDLMbmrCT2li3eVB72/gF63BPS8pWEZrjlAMRKA8FDw==} + '@seald-io/nedb@4.0.4': dependencies: '@seald-io/binary-search-tree': 1.0.3 localforage: 1.10.0 util: 0.12.5 - dev: false - /@semantic-release/commit-analyzer@11.1.0(semantic-release@22.0.12): - resolution: {integrity: sha512-cXNTbv3nXR2hlzHjAMgbuiQVtvWHTlwwISt60B+4NZv01y/QRY7p2HcJm8Eh2StzcTJoNnflvKjHH/cjFS7d5g==} - engines: {node: ^18.17 || >=20.6.1} - peerDependencies: - semantic-release: '>=20.1.0' + '@semantic-release/commit-analyzer@11.1.0(semantic-release@22.0.12(typescript@5.4.5))': dependencies: conventional-changelog-angular: 7.0.0 conventional-commits-filter: 4.0.0 conventional-commits-parser: 5.0.0 debug: 4.3.4 - import-from-esm: 1.3.3 + import-from-esm: 1.3.4 lodash-es: 4.17.21 micromatch: 4.0.5 - semantic-release: 22.0.12(typescript@5.3.3) + semantic-release: 22.0.12(typescript@5.4.5) transitivePeerDependencies: - supports-color - dev: true - /@semantic-release/error@3.0.0: - resolution: {integrity: sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==} - engines: {node: '>=14.17'} - dev: true + '@semantic-release/error@3.0.0': {} - /@semantic-release/error@4.0.0: - resolution: {integrity: sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==} - engines: {node: '>=18'} - dev: true + '@semantic-release/error@4.0.0': {} - /@semantic-release/exec@6.0.3(semantic-release@22.0.12): - resolution: {integrity: sha512-bxAq8vLOw76aV89vxxICecEa8jfaWwYITw6X74zzlO0mc/Bgieqx9kBRz9z96pHectiTAtsCwsQcUyLYWnp3VQ==} - engines: {node: '>=14.17'} - peerDependencies: - semantic-release: '>=18.0.0' + '@semantic-release/exec@6.0.3(semantic-release@22.0.12(typescript@5.4.5))': dependencies: '@semantic-release/error': 3.0.0 aggregate-error: 3.1.0 @@ -2908,21 +8112,16 @@ packages: execa: 5.1.1 lodash: 4.17.21 parse-json: 5.2.0 - semantic-release: 22.0.12(typescript@5.3.3) + semantic-release: 22.0.12(typescript@5.4.5) transitivePeerDependencies: - supports-color - dev: true - /@semantic-release/github@9.2.6(semantic-release@22.0.12): - resolution: {integrity: sha512-shi+Lrf6exeNZF+sBhK+P011LSbhmIAoUEgEY6SsxF8irJ+J2stwI5jkyDQ+4gzYyDImzV6LCKdYB9FXnQRWKA==} - engines: {node: '>=18'} - peerDependencies: - semantic-release: '>=20.1.0' + '@semantic-release/github@9.2.6(semantic-release@22.0.12(typescript@5.4.5))': dependencies: - '@octokit/core': 5.1.0 - '@octokit/plugin-paginate-rest': 9.2.0(@octokit/core@5.1.0) - '@octokit/plugin-retry': 6.0.1(@octokit/core@5.1.0) - '@octokit/plugin-throttling': 8.2.0(@octokit/core@5.1.0) + '@octokit/core': 5.2.0 + '@octokit/plugin-paginate-rest': 9.2.1(@octokit/core@5.2.0) + '@octokit/plugin-retry': 6.0.1(@octokit/core@5.2.0) + '@octokit/plugin-throttling': 8.2.0(@octokit/core@5.2.0) '@semantic-release/error': 4.0.0 aggregate-error: 5.0.0 debug: 4.3.4 @@ -2932,19 +8131,14 @@ packages: https-proxy-agent: 7.0.4 issue-parser: 6.0.0 lodash-es: 4.17.21 - mime: 4.0.1 + mime: 4.0.3 p-filter: 4.1.0 - semantic-release: 22.0.12(typescript@5.3.3) + semantic-release: 22.0.12(typescript@5.4.5) url-join: 5.0.0 transitivePeerDependencies: - supports-color - dev: true - /@semantic-release/npm@11.0.2(semantic-release@22.0.12): - resolution: {integrity: sha512-owtf3RjyPvRE63iUKZ5/xO4uqjRpVQDUB9+nnXj0xwfIeM9pRl+cG+zGDzdftR4m3f2s4Wyf3SexW+kF5DFtWA==} - engines: {node: ^18.17 || >=20} - peerDependencies: - semantic-release: '>=20.1.0' + '@semantic-release/npm@11.0.3(semantic-release@22.0.12(typescript@5.4.5))': dependencies: '@semantic-release/error': 4.0.0 aggregate-error: 5.0.0 @@ -2952,21 +8146,16 @@ packages: fs-extra: 11.2.0 lodash-es: 4.17.21 nerf-dart: 1.0.0 - normalize-url: 8.0.0 - npm: 10.4.0 + normalize-url: 8.0.1 + npm: 10.7.0 rc: 1.2.8 read-pkg: 9.0.1 registry-auth-token: 5.0.2 - semantic-release: 22.0.12(typescript@5.3.3) - semver: 7.5.4 + semantic-release: 22.0.12(typescript@5.4.5) + semver: 7.6.2 tempy: 3.1.0 - dev: true - /@semantic-release/release-notes-generator@12.1.0(semantic-release@22.0.12): - resolution: {integrity: sha512-g6M9AjUKAZUZnxaJZnouNBeDNTCUrJ5Ltj+VJ60gJeDaRRahcHsry9HW8yKrnKkKNkx5lbWiEP1FPMqVNQz8Kg==} - engines: {node: ^18.17 || >=20.6.1} - peerDependencies: - semantic-release: '>=20.1.0' + '@semantic-release/release-notes-generator@12.1.0(semantic-release@22.0.12(typescript@5.4.5))': dependencies: conventional-changelog-angular: 7.0.0 conventional-changelog-writer: 7.0.1 @@ -2974,584 +8163,417 @@ packages: conventional-commits-parser: 5.0.0 debug: 4.3.4 get-stream: 7.0.1 - import-from-esm: 1.3.3 + import-from-esm: 1.3.4 into-stream: 7.0.0 lodash-es: 4.17.21 read-pkg-up: 11.0.0 - semantic-release: 22.0.12(typescript@5.3.3) + semantic-release: 22.0.12(typescript@5.4.5) transitivePeerDependencies: - supports-color - dev: true - - /@sinclair/typebox@0.27.8: - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - dev: true - /@sindresorhus/is@4.6.0: - resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} - engines: {node: '>=10'} + '@sinclair/typebox@0.27.8': {} - /@sindresorhus/merge-streams@1.0.0: - resolution: {integrity: sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==} - engines: {node: '>=18'} - dev: true + '@sindresorhus/is@4.6.0': {} - /@sindresorhus/merge-streams@2.3.0: - resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} - engines: {node: '>=18'} - dev: true + '@sindresorhus/merge-streams@2.3.0': {} - /@sinonjs/commons@2.0.0: - resolution: {integrity: sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==} + '@sinonjs/commons@2.0.0': dependencies: type-detect: 4.0.8 - dev: true - /@sinonjs/commons@3.0.1: - resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + '@sinonjs/commons@3.0.1': dependencies: type-detect: 4.0.8 - dev: true - /@sinonjs/fake-timers@10.3.0: - resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@sinonjs/fake-timers@10.3.0': dependencies: '@sinonjs/commons': 3.0.1 - dev: true - /@sinonjs/fake-timers@11.2.2: - resolution: {integrity: sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==} + '@sinonjs/fake-timers@11.2.2': dependencies: '@sinonjs/commons': 3.0.1 - dev: true - /@sinonjs/samsam@8.0.0: - resolution: {integrity: sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==} + '@sinonjs/samsam@8.0.0': dependencies: '@sinonjs/commons': 2.0.0 lodash.get: 4.4.2 type-detect: 4.0.8 - dev: true - /@sinonjs/text-encoding@0.7.2: - resolution: {integrity: sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==} - dev: true + '@sinonjs/text-encoding@0.7.2': {} - /@smithy/abort-controller@1.1.0: - resolution: {integrity: sha512-5imgGUlZL4dW4YWdMYAKLmal9ny/tlenM81QZY7xYyb76z9Z/QOg7oM5Ak9HQl8QfFTlGVWwcMXl+54jroRgEQ==} - engines: {node: '>=14.0.0'} + '@smithy/abort-controller@2.2.0': dependencies: - '@smithy/types': 1.2.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@smithy/config-resolver@1.1.0: - resolution: {integrity: sha512-7WD9eZHp46BxAjNGHJLmxhhyeiNWkBdVStd7SUJPUZqQGeIO/REtIrcIfKUfdiHTQ9jyu2SYoqvzqqaFc6987w==} - engines: {node: '>=14.0.0'} + '@smithy/chunked-blob-reader-native@2.2.0': dependencies: - '@smithy/types': 1.2.0 - '@smithy/util-config-provider': 1.1.0 - '@smithy/util-middleware': 1.1.0 + '@smithy/util-base64': 2.3.0 tslib: 2.6.2 - dev: false - /@smithy/credential-provider-imds@1.1.0: - resolution: {integrity: sha512-kUMOdEu3RP6ozH0Ga8OeMP8gSkBsK1UqZZKyPLFnpZHrtZuHSSt7M7gsHYB/bYQBZAo3o7qrGmRty3BubYtYxQ==} - engines: {node: '>=14.0.0'} + '@smithy/chunked-blob-reader@2.2.0': + dependencies: + tslib: 2.6.2 + + '@smithy/config-resolver@2.2.0': dependencies: - '@smithy/node-config-provider': 1.1.0 - '@smithy/property-provider': 1.2.0 - '@smithy/types': 1.2.0 - '@smithy/url-parser': 1.1.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/types': 2.12.0 + '@smithy/util-config-provider': 2.3.0 + '@smithy/util-middleware': 2.2.0 tslib: 2.6.2 - dev: false - /@smithy/eventstream-codec@1.1.0: - resolution: {integrity: sha512-3tEbUb8t8an226jKB6V/Q2XU/J53lCwCzULuBPEaF4JjSh+FlCMp7TmogE/Aij5J9DwlsZ4VAD/IRDuQ/0ZtMw==} + '@smithy/core@1.4.2': + dependencies: + '@smithy/middleware-endpoint': 2.5.1 + '@smithy/middleware-retry': 2.3.1 + '@smithy/middleware-serde': 2.3.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.1 + '@smithy/types': 2.12.0 + '@smithy/util-middleware': 2.2.0 + tslib: 2.6.2 + + '@smithy/credential-provider-imds@2.3.0': + dependencies: + '@smithy/node-config-provider': 2.3.0 + '@smithy/property-provider': 2.2.0 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + tslib: 2.6.2 + + '@smithy/eventstream-codec@2.2.0': dependencies: '@aws-crypto/crc32': 3.0.0 - '@smithy/types': 1.2.0 - '@smithy/util-hex-encoding': 1.1.0 + '@smithy/types': 2.12.0 + '@smithy/util-hex-encoding': 2.2.0 tslib: 2.6.2 - dev: false - /@smithy/eventstream-serde-browser@1.1.0: - resolution: {integrity: sha512-qUov6SYlcCeubwTQgaSBuNO0J31UdwgGRSZvmHhc3CCYOywoVSsA5vahcNuhoZDzZkhWTpol3Pm7+6OUuHF0aA==} - engines: {node: '>=14.0.0'} + '@smithy/eventstream-serde-browser@2.2.0': dependencies: - '@smithy/eventstream-serde-universal': 1.1.0 - '@smithy/types': 1.2.0 + '@smithy/eventstream-serde-universal': 2.2.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@smithy/eventstream-serde-config-resolver@1.1.0: - resolution: {integrity: sha512-vtPnp8FJkrNibWZCXvJN6rijTAEAzrmEKNfCUJOHAeBScY25hc6NjYlEJfdSmhW1qaA179oXeqHobcUNzvFkmw==} - engines: {node: '>=14.0.0'} + '@smithy/eventstream-serde-config-resolver@2.2.0': dependencies: - '@smithy/types': 1.2.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@smithy/eventstream-serde-node@1.1.0: - resolution: {integrity: sha512-r8kUOPsJMolBGi/eU2gKfw5spfAhQjJXLe4bjjTzkapsqL654JZ+8G9iS1TprYUcCoCHDbwnH1of3kjrYKk7CQ==} - engines: {node: '>=14.0.0'} + '@smithy/eventstream-serde-node@2.2.0': dependencies: - '@smithy/eventstream-serde-universal': 1.1.0 - '@smithy/types': 1.2.0 + '@smithy/eventstream-serde-universal': 2.2.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@smithy/eventstream-serde-universal@1.1.0: - resolution: {integrity: sha512-8nQttgdbefJbLfz7Mao0FtkdRUlc92fCiHV3vClAl1N/qetm/I6Lsu5mLt9CzG7TGFkFb5t3qzAV2FaeAqF+ag==} - engines: {node: '>=14.0.0'} + '@smithy/eventstream-serde-universal@2.2.0': dependencies: - '@smithy/eventstream-codec': 1.1.0 - '@smithy/types': 1.2.0 + '@smithy/eventstream-codec': 2.2.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@smithy/fetch-http-handler@1.1.0: - resolution: {integrity: sha512-N22C9R44u5WGlcY+Wuv8EXmCAq62wWwriRAuoczMEwAIjPbvHSthyPSLqI4S7kAST1j6niWg8kwpeJ3ReAv3xg==} + '@smithy/fetch-http-handler@2.5.0': dependencies: - '@smithy/protocol-http': 1.2.0 - '@smithy/querystring-builder': 1.1.0 - '@smithy/types': 1.2.0 - '@smithy/util-base64': 1.1.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/querystring-builder': 2.2.0 + '@smithy/types': 2.12.0 + '@smithy/util-base64': 2.3.0 tslib: 2.6.2 - dev: false - /@smithy/hash-node@1.1.0: - resolution: {integrity: sha512-yiNKDGMzrQjnpnbLfkYKo+HwIxmBAsv0AI++QIJwvhfkLpUTBylelkv6oo78/YqZZS6h+bGfl0gILJsKE2wAKQ==} - engines: {node: '>=14.0.0'} + '@smithy/hash-blob-browser@2.2.0': dependencies: - '@smithy/types': 1.2.0 - '@smithy/util-buffer-from': 1.1.0 - '@smithy/util-utf8': 1.1.0 + '@smithy/chunked-blob-reader': 2.2.0 + '@smithy/chunked-blob-reader-native': 2.2.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@smithy/invalid-dependency@1.1.0: - resolution: {integrity: sha512-h2rXn68ClTwzPXYzEUNkz+0B/A0Hz8YdFNTiEwlxkwzkETGKMxmsrQGFXwYm3jd736R5vkXcClXz1ddKrsaBEQ==} + '@smithy/hash-node@2.2.0': dependencies: - '@smithy/types': 1.2.0 + '@smithy/types': 2.12.0 + '@smithy/util-buffer-from': 2.2.0 + '@smithy/util-utf8': 2.3.0 tslib: 2.6.2 - dev: false - /@smithy/is-array-buffer@1.1.0: - resolution: {integrity: sha512-twpQ/n+3OWZJ7Z+xu43MJErmhB/WO/mMTnqR6PwWQShvSJ/emx5d1N59LQZk6ZpTAeuRWrc+eHhkzTp9NFjNRQ==} - engines: {node: '>=14.0.0'} + '@smithy/hash-stream-node@2.2.0': dependencies: + '@smithy/types': 2.12.0 + '@smithy/util-utf8': 2.3.0 tslib: 2.6.2 - dev: false - /@smithy/middleware-content-length@1.1.0: - resolution: {integrity: sha512-iNxwhZ7Xc5+LjeDElEOi/Nh8fFsc9Dw9+5w7h7/GLFIU0RgAwBJuJtcP1vNTOwzW4B3hG+gRu8sQLqA9OEaTwA==} - engines: {node: '>=14.0.0'} + '@smithy/invalid-dependency@2.2.0': dependencies: - '@smithy/protocol-http': 1.2.0 - '@smithy/types': 1.2.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@smithy/middleware-endpoint@1.1.0: - resolution: {integrity: sha512-PvpazNjVpxX2ICrzoFYCpFnjB39DKCpZds8lRpAB3p6HGrx6QHBaNvOzVhJGBf0jcAbfCdc5/W0n9z8VWaSSww==} - engines: {node: '>=14.0.0'} + '@smithy/is-array-buffer@2.2.0': dependencies: - '@smithy/middleware-serde': 1.1.0 - '@smithy/types': 1.2.0 - '@smithy/url-parser': 1.1.0 - '@smithy/util-middleware': 1.1.0 tslib: 2.6.2 - dev: false - /@smithy/middleware-retry@1.1.0: - resolution: {integrity: sha512-lINKYxIvT+W20YFOtHBKeGm7npuJg0/YCoShttU7fVpsmU+a2rdb9zrJn1MHqWfUL6DhTAWGa0tH2O7l4XrDcw==} - engines: {node: '>=14.0.0'} + '@smithy/md5-js@2.2.0': dependencies: - '@smithy/protocol-http': 1.2.0 - '@smithy/service-error-classification': 1.1.0 - '@smithy/types': 1.2.0 - '@smithy/util-middleware': 1.1.0 - '@smithy/util-retry': 1.1.0 + '@smithy/types': 2.12.0 + '@smithy/util-utf8': 2.3.0 tslib: 2.6.2 - uuid: 8.3.2 - dev: false - /@smithy/middleware-serde@1.1.0: - resolution: {integrity: sha512-RiBMxhxuO9VTjHsjJvhzViyceoLhU6gtrnJGpAXY43wE49IstXIGEQz8MT50/hOq5EumX16FCpup0r5DVyfqNQ==} - engines: {node: '>=14.0.0'} + '@smithy/middleware-content-length@2.2.0': dependencies: - '@smithy/types': 1.2.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@smithy/middleware-stack@1.1.0: - resolution: {integrity: sha512-XynYiIvXNea2BbLcppvpNK0zu8o2woJqgnmxqYTn4FWagH/Hr2QIk8LOsUz7BIJ4tooFhmx8urHKCdlPbbPDCA==} - engines: {node: '>=14.0.0'} + '@smithy/middleware-endpoint@2.5.1': dependencies: + '@smithy/middleware-serde': 2.3.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + '@smithy/util-middleware': 2.2.0 tslib: 2.6.2 - dev: false - /@smithy/node-config-provider@1.1.0: - resolution: {integrity: sha512-2G4TlzUnmTrUY26VKTonQqydwb+gtM/mcl+TqDP8CnWtJKVL8ElPpKgLGScP04bPIRY9x2/10lDdoaRXDqPuCw==} - engines: {node: '>=14.0.0'} + '@smithy/middleware-retry@2.3.1': dependencies: - '@smithy/property-provider': 1.2.0 - '@smithy/shared-ini-file-loader': 1.1.0 - '@smithy/types': 1.2.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/service-error-classification': 2.1.5 + '@smithy/smithy-client': 2.5.1 + '@smithy/types': 2.12.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-retry': 2.2.0 tslib: 2.6.2 - dev: false + uuid: 9.0.1 - /@smithy/node-http-handler@1.1.0: - resolution: {integrity: sha512-d3kRriEgaIiGXLziAM8bjnaLn1fthCJeTLZIwEIpzQqe6yPX0a+yQoLCTyjb2fvdLwkMoG4p7THIIB5cj5lkbg==} - engines: {node: '>=14.0.0'} + '@smithy/middleware-serde@2.3.0': dependencies: - '@smithy/abort-controller': 1.1.0 - '@smithy/protocol-http': 1.2.0 - '@smithy/querystring-builder': 1.1.0 - '@smithy/types': 1.2.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@smithy/property-provider@1.2.0: - resolution: {integrity: sha512-qlJd9gT751i4T0t/hJAyNGfESfi08Fek8QiLcysoKPgR05qHhG0OYhlaCJHhpXy4ECW0lHyjvFM1smrCLIXVfw==} - engines: {node: '>=14.0.0'} + '@smithy/middleware-stack@2.2.0': dependencies: - '@smithy/types': 1.2.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@smithy/protocol-http@1.2.0: - resolution: {integrity: sha512-GfGfruksi3nXdFok5RhgtOnWe5f6BndzYfmEXISD+5gAGdayFGpjWu5pIqIweTudMtse20bGbc+7MFZXT1Tb8Q==} - engines: {node: '>=14.0.0'} + '@smithy/node-config-provider@2.3.0': dependencies: - '@smithy/types': 1.2.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@smithy/querystring-builder@1.1.0: - resolution: {integrity: sha512-gDEi4LxIGLbdfjrjiY45QNbuDmpkwh9DX4xzrR2AzjjXpxwGyfSpbJaYhXARw9p17VH0h9UewnNQXNwaQyYMDA==} - engines: {node: '>=14.0.0'} + '@smithy/node-http-handler@2.5.0': dependencies: - '@smithy/types': 1.2.0 - '@smithy/util-uri-escape': 1.1.0 + '@smithy/abort-controller': 2.2.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/querystring-builder': 2.2.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@smithy/querystring-parser@1.1.0: - resolution: {integrity: sha512-Lm/FZu2qW3XX+kZ4WPwr+7aAeHf1Lm84UjNkKyBu16XbmEV7ukfhXni2aIwS2rcVf8Yv5E7wchGGpOFldj9V4Q==} - engines: {node: '>=14.0.0'} + '@smithy/property-provider@2.2.0': dependencies: - '@smithy/types': 1.2.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@smithy/service-error-classification@1.1.0: - resolution: {integrity: sha512-OCTEeJ1igatd5kFrS2VDlYbainNNpf7Lj1siFOxnRWqYOP9oNvC5HOJBd3t+Z8MbrmehBtuDJ2QqeBsfeiNkww==} - engines: {node: '>=14.0.0'} - dev: false + '@smithy/protocol-http@3.3.0': + dependencies: + '@smithy/types': 2.12.0 + tslib: 2.6.2 - /@smithy/shared-ini-file-loader@1.1.0: - resolution: {integrity: sha512-S/v33zvCWzFyGZGlsEF0XsZtNNR281UhR7byk3nRfsgw5lGpg51rK/zjMgulM+h6NSuXaFILaYrw1I1v4kMcuA==} - engines: {node: '>=14.0.0'} + '@smithy/querystring-builder@2.2.0': dependencies: - '@smithy/types': 1.2.0 + '@smithy/types': 2.12.0 + '@smithy/util-uri-escape': 2.2.0 tslib: 2.6.2 - dev: false - /@smithy/signature-v4@1.1.0: - resolution: {integrity: sha512-fDo3m7YqXBs7neciOePPd/X9LPm5QLlDMdIC4m1H6dgNLnXfLMFNIxEfPyohGA8VW9Wn4X8lygnPSGxDZSmp0Q==} - engines: {node: '>=14.0.0'} + '@smithy/querystring-parser@2.2.0': dependencies: - '@smithy/eventstream-codec': 1.1.0 - '@smithy/is-array-buffer': 1.1.0 - '@smithy/types': 1.2.0 - '@smithy/util-hex-encoding': 1.1.0 - '@smithy/util-middleware': 1.1.0 - '@smithy/util-uri-escape': 1.1.0 - '@smithy/util-utf8': 1.1.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@smithy/smithy-client@1.1.0: - resolution: {integrity: sha512-j32SGgVhv2G9nBTmel9u3OXux8KG20ssxuFakJrEeDug3kqbl1qrGzVLCe+Eib402UDtA0Sp1a4NZ2SEXDBxag==} - engines: {node: '>=14.0.0'} + '@smithy/service-error-classification@2.1.5': + dependencies: + '@smithy/types': 2.12.0 + + '@smithy/shared-ini-file-loader@2.4.0': dependencies: - '@smithy/middleware-stack': 1.1.0 - '@smithy/types': 1.2.0 - '@smithy/util-stream': 1.1.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@smithy/types@1.2.0: - resolution: {integrity: sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA==} - engines: {node: '>=14.0.0'} + '@smithy/signature-v4@2.3.0': dependencies: + '@smithy/is-array-buffer': 2.2.0 + '@smithy/types': 2.12.0 + '@smithy/util-hex-encoding': 2.2.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-uri-escape': 2.2.0 + '@smithy/util-utf8': 2.3.0 tslib: 2.6.2 - dev: false - /@smithy/url-parser@1.1.0: - resolution: {integrity: sha512-tpvi761kzboiLNGEWczuybMPCJh6WHB3cz9gWAG95mSyaKXmmX8ZcMxoV+irZfxDqLwZVJ22XTumu32S7Ow8aQ==} + '@smithy/smithy-client@2.5.1': dependencies: - '@smithy/querystring-parser': 1.1.0 - '@smithy/types': 1.2.0 + '@smithy/middleware-endpoint': 2.5.1 + '@smithy/middleware-stack': 2.2.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/types': 2.12.0 + '@smithy/util-stream': 2.2.0 tslib: 2.6.2 - dev: false - /@smithy/util-base64@1.1.0: - resolution: {integrity: sha512-FpYmDmVbOXAxqvoVCwqehUN0zXS+lN8V7VS9O7I8MKeVHdSTsZzlwiMEvGoyTNOXWn8luF4CTDYgNHnZViR30g==} - engines: {node: '>=14.0.0'} + '@smithy/types@2.12.0': dependencies: - '@smithy/util-buffer-from': 1.1.0 tslib: 2.6.2 - dev: false - /@smithy/util-body-length-browser@1.1.0: - resolution: {integrity: sha512-cep3ioRxzRZ2Jbp3Kly7gy6iNVefYXiT6ETt8W01RQr3uwi1YMkrbU1p3lMR4KhX/91Nrk6UOgX1RH+oIt48RQ==} + '@smithy/url-parser@2.2.0': dependencies: + '@smithy/querystring-parser': 2.2.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@smithy/util-body-length-node@1.1.0: - resolution: {integrity: sha512-fRHRjkUuT5em4HZoshySXmB1n3HAU7IS232s+qU4TicexhyGJpXMK/2+c56ePOIa1FOK2tV1Q3J/7Mae35QVSw==} - engines: {node: '>=14.0.0'} + '@smithy/util-base64@2.3.0': dependencies: + '@smithy/util-buffer-from': 2.2.0 + '@smithy/util-utf8': 2.3.0 tslib: 2.6.2 - dev: false - /@smithy/util-buffer-from@1.1.0: - resolution: {integrity: sha512-9m6NXE0ww+ra5HKHCHig20T+FAwxBAm7DIdwc/767uGWbRcY720ybgPacQNB96JMOI7xVr/CDa3oMzKmW4a+kw==} - engines: {node: '>=14.0.0'} + '@smithy/util-body-length-browser@2.2.0': dependencies: - '@smithy/is-array-buffer': 1.1.0 tslib: 2.6.2 - dev: false - /@smithy/util-config-provider@1.1.0: - resolution: {integrity: sha512-rQ47YpNmF6Is4I9GiE3T3+0xQ+r7RKRKbmHYyGSbyep/0cSf9kteKcI0ssJTvveJ1K4QvwrxXj1tEFp/G2UqxQ==} - engines: {node: '>=14.0.0'} + '@smithy/util-body-length-node@2.3.0': dependencies: tslib: 2.6.2 - dev: false - /@smithy/util-defaults-mode-browser@1.1.0: - resolution: {integrity: sha512-0bWhs1e412bfC5gwPCMe8Zbz0J8UoZ/meEQdo6MYj8Ne+c+QZ+KxVjx0a1dFYOclvM33SslL9dP0odn8kfblkg==} - engines: {node: '>= 10.0.0'} + '@smithy/util-buffer-from@2.2.0': + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.6.2 + + '@smithy/util-config-provider@2.3.0': + dependencies: + tslib: 2.6.2 + + '@smithy/util-defaults-mode-browser@2.2.1': + dependencies: + '@smithy/property-provider': 2.2.0 + '@smithy/smithy-client': 2.5.1 + '@smithy/types': 2.12.0 + bowser: 2.11.0 + tslib: 2.6.2 + + '@smithy/util-defaults-mode-node@2.3.1': dependencies: - '@smithy/property-provider': 1.2.0 - '@smithy/types': 1.2.0 - bowser: 2.11.0 + '@smithy/config-resolver': 2.2.0 + '@smithy/credential-provider-imds': 2.3.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/property-provider': 2.2.0 + '@smithy/smithy-client': 2.5.1 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@smithy/util-defaults-mode-node@1.1.0: - resolution: {integrity: sha512-440e25TUH2b+TeK5CwsjYFrI9ShVOgA31CoxCKiv4ncSK4ZM68XW5opYxQmzMbRWARGEMu2XEUeBmOgMU2RLsw==} - engines: {node: '>= 10.0.0'} + '@smithy/util-endpoints@1.2.0': dependencies: - '@smithy/config-resolver': 1.1.0 - '@smithy/credential-provider-imds': 1.1.0 - '@smithy/node-config-provider': 1.1.0 - '@smithy/property-provider': 1.2.0 - '@smithy/types': 1.2.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@smithy/util-hex-encoding@1.1.0: - resolution: {integrity: sha512-7UtIE9eH0u41zpB60Jzr0oNCQ3hMJUabMcKRUVjmyHTXiWDE4vjSqN6qlih7rCNeKGbioS7f/y2Jgym4QZcKFg==} - engines: {node: '>=14.0.0'} + '@smithy/util-hex-encoding@2.2.0': dependencies: tslib: 2.6.2 - dev: false - /@smithy/util-middleware@1.1.0: - resolution: {integrity: sha512-6hhckcBqVgjWAqLy2vqlPZ3rfxLDhFWEmM7oLh2POGvsi7j0tHkbN7w4DFhuBExVJAbJ/qqxqZdRY6Fu7/OezQ==} - engines: {node: '>=14.0.0'} + '@smithy/util-middleware@2.2.0': dependencies: + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@smithy/util-retry@1.1.0: - resolution: {integrity: sha512-ygQW5HBqYXpR3ua09UciS0sL7UGJzGiktrKkOuEJwARoUuzz40yaEGU6xd9Gs7KBmAaFC8gMfnghHtwZ2nyBCQ==} - engines: {node: '>= 14.0.0'} + '@smithy/util-retry@2.2.0': dependencies: - '@smithy/service-error-classification': 1.1.0 + '@smithy/service-error-classification': 2.1.5 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@smithy/util-stream@1.1.0: - resolution: {integrity: sha512-w3lsdGsntaLQIrwDWJkIFKrFscgZXwU/oxsse09aSTNv5TckPhDeYea3LhsDrU5MGAG3vprhVZAKr33S45coVA==} - engines: {node: '>=14.0.0'} + '@smithy/util-stream@2.2.0': dependencies: - '@smithy/fetch-http-handler': 1.1.0 - '@smithy/node-http-handler': 1.1.0 - '@smithy/types': 1.2.0 - '@smithy/util-base64': 1.1.0 - '@smithy/util-buffer-from': 1.1.0 - '@smithy/util-hex-encoding': 1.1.0 - '@smithy/util-utf8': 1.1.0 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/types': 2.12.0 + '@smithy/util-base64': 2.3.0 + '@smithy/util-buffer-from': 2.2.0 + '@smithy/util-hex-encoding': 2.2.0 + '@smithy/util-utf8': 2.3.0 tslib: 2.6.2 - dev: false - /@smithy/util-uri-escape@1.1.0: - resolution: {integrity: sha512-/jL/V1xdVRt5XppwiaEU8Etp5WHZj609n0xMTuehmCqdoOFbId1M+aEeDWZsQ+8JbEB/BJ6ynY2SlYmOaKtt8w==} - engines: {node: '>=14.0.0'} + '@smithy/util-uri-escape@2.2.0': dependencies: tslib: 2.6.2 - dev: false - /@smithy/util-utf8@1.1.0: - resolution: {integrity: sha512-p/MYV+JmqmPyjdgyN2UxAeYDj9cBqCjp0C/NsTWnnjoZUVqoeZ6IrW915L9CAKWVECgv9lVQGc4u/yz26/bI1A==} - engines: {node: '>=14.0.0'} + '@smithy/util-utf8@2.3.0': dependencies: - '@smithy/util-buffer-from': 1.1.0 + '@smithy/util-buffer-from': 2.2.0 tslib: 2.6.2 - dev: false - /@smithy/util-waiter@1.1.0: - resolution: {integrity: sha512-S6FNIB3UJT+5Efd/0DeziO5Rs82QAMODHW4v2V3oNRrwaBigY/7Yx3SiLudZuF9WpVsV08Ih3BjIH34nzZiinQ==} - engines: {node: '>=14.0.0'} + '@smithy/util-waiter@2.2.0': dependencies: - '@smithy/abort-controller': 1.1.0 - '@smithy/types': 1.2.0 + '@smithy/abort-controller': 2.2.0 + '@smithy/types': 2.12.0 tslib: 2.6.2 - dev: false - /@swc/core-darwin-arm64@1.4.2: - resolution: {integrity: sha512-1uSdAn1MRK5C1m/TvLZ2RDvr0zLvochgrZ2xL+lRzugLlCTlSA+Q4TWtrZaOz+vnnFVliCpw7c7qu0JouhgQIw==} - engines: {node: '>=10'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true + '@swc/core-darwin-arm64@1.5.7': optional: true - /@swc/core-darwin-x64@1.4.2: - resolution: {integrity: sha512-TYD28+dCQKeuxxcy7gLJUCFLqrwDZnHtC2z7cdeGfZpbI2mbfppfTf2wUPzqZk3gEC96zHd4Yr37V3Tvzar+lQ==} - engines: {node: '>=10'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true + '@swc/core-darwin-x64@1.5.7': optional: true - /@swc/core-linux-arm-gnueabihf@1.4.2: - resolution: {integrity: sha512-Eyqipf7ZPGj0vplKHo8JUOoU1un2sg5PjJMpEesX0k+6HKE2T8pdyeyXODN0YTFqzndSa/J43EEPXm+rHAsLFQ==} - engines: {node: '>=10'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true + '@swc/core-linux-arm-gnueabihf@1.5.7': optional: true - /@swc/core-linux-arm64-gnu@1.4.2: - resolution: {integrity: sha512-wZn02DH8VYPv3FC0ub4my52Rttsus/rFw+UUfzdb3tHMHXB66LqN+rR0ssIOZrH6K+VLN6qpTw9VizjyoH0BxA==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true + '@swc/core-linux-arm64-gnu@1.5.7': optional: true - /@swc/core-linux-arm64-musl@1.4.2: - resolution: {integrity: sha512-3G0D5z9hUj9bXNcwmA1eGiFTwe5rWkuL3DsoviTj73TKLpk7u64ND0XjEfO0huVv4vVu9H1jodrKb7nvln/dlw==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true + '@swc/core-linux-arm64-musl@1.5.7': optional: true - /@swc/core-linux-x64-gnu@1.4.2: - resolution: {integrity: sha512-LFxn9U8cjmYHw3jrdPNqPAkBGglKE3tCZ8rA7hYyp0BFxuo7L2ZcEnPm4RFpmSCCsExFH+LEJWuMGgWERoktvg==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true + '@swc/core-linux-x64-gnu@1.5.7': optional: true - /@swc/core-linux-x64-musl@1.4.2: - resolution: {integrity: sha512-dp0fAmreeVVYTUcb4u9njTPrYzKnbIH0EhH2qvC9GOYNNREUu2GezSIDgonjOXkHiTCvopG4xU7y56XtXj4VrQ==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true + '@swc/core-linux-x64-musl@1.5.7': optional: true - /@swc/core-win32-arm64-msvc@1.4.2: - resolution: {integrity: sha512-HlVIiLMQkzthAdqMslQhDkoXJ5+AOLUSTV6fm6shFKZKqc/9cJvr4S8UveNERL9zUficA36yM3bbfo36McwnvQ==} - engines: {node: '>=10'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true + '@swc/core-win32-arm64-msvc@1.5.7': optional: true - /@swc/core-win32-ia32-msvc@1.4.2: - resolution: {integrity: sha512-WCF8faPGjCl4oIgugkp+kL9nl3nUATlzKXCEGFowMEmVVCFM0GsqlmGdPp1pjZoWc9tpYanoXQDnp5IvlDSLhA==} - engines: {node: '>=10'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true + '@swc/core-win32-ia32-msvc@1.5.7': optional: true - /@swc/core-win32-x64-msvc@1.4.2: - resolution: {integrity: sha512-oV71rwiSpA5xre2C5570BhCsg1HF97SNLsZ/12xv7zayGzqr3yvFALFJN8tHKpqUdCB4FGPjoP3JFdV3i+1wUw==} - engines: {node: '>=10'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true + '@swc/core-win32-x64-msvc@1.5.7': optional: true - /@swc/core@1.4.2: - resolution: {integrity: sha512-vWgY07R/eqj1/a0vsRKLI9o9klGZfpLNOVEnrv4nrccxBgYPjcf22IWwAoaBJ+wpA7Q4fVjCUM8lP0m01dpxcg==} - engines: {node: '>=10'} - requiresBuild: true - peerDependencies: - '@swc/helpers': ^0.5.0 - peerDependenciesMeta: - '@swc/helpers': - optional: true + '@swc/core@1.5.7': dependencies: '@swc/counter': 0.1.3 - '@swc/types': 0.1.5 + '@swc/types': 0.1.7 optionalDependencies: - '@swc/core-darwin-arm64': 1.4.2 - '@swc/core-darwin-x64': 1.4.2 - '@swc/core-linux-arm-gnueabihf': 1.4.2 - '@swc/core-linux-arm64-gnu': 1.4.2 - '@swc/core-linux-arm64-musl': 1.4.2 - '@swc/core-linux-x64-gnu': 1.4.2 - '@swc/core-linux-x64-musl': 1.4.2 - '@swc/core-win32-arm64-msvc': 1.4.2 - '@swc/core-win32-ia32-msvc': 1.4.2 - '@swc/core-win32-x64-msvc': 1.4.2 - dev: true - - /@swc/counter@0.1.3: - resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - dev: true + '@swc/core-darwin-arm64': 1.5.7 + '@swc/core-darwin-x64': 1.5.7 + '@swc/core-linux-arm-gnueabihf': 1.5.7 + '@swc/core-linux-arm64-gnu': 1.5.7 + '@swc/core-linux-arm64-musl': 1.5.7 + '@swc/core-linux-x64-gnu': 1.5.7 + '@swc/core-linux-x64-musl': 1.5.7 + '@swc/core-win32-arm64-msvc': 1.5.7 + '@swc/core-win32-ia32-msvc': 1.5.7 + '@swc/core-win32-x64-msvc': 1.5.7 - /@swc/types@0.1.5: - resolution: {integrity: sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==} - dev: true + '@swc/counter@0.1.3': {} - /@szmarczak/http-timer@4.0.6: - resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} - engines: {node: '>=10'} + '@swc/types@0.1.7': + dependencies: + '@swc/counter': 0.1.3 + + '@szmarczak/http-timer@4.0.6': dependencies: defer-to-connect: 2.0.1 - dev: false - /@thi.ng/api@7.2.0: - resolution: {integrity: sha512-4NcwHXxwPF/JgJG/jSFd9rjfQNguF0QrHvd6e+CEf4T0sFChqetW6ZmJ6/a2X+noDVntgulegA+Bx0HHzw+Tyw==} - dev: false + '@thi.ng/api@7.2.0': {} - /@thi.ng/arrays@1.0.3: - resolution: {integrity: sha512-ZUB27bdpTwcvxYJTlt/eWKrj98nWXo0lAUPwRwubk4GlH8rTKKkc7qZr9/4LCKPsNjnZdQqbBtNvNf3HjYxCzw==} + '@thi.ng/arrays@1.0.3': dependencies: '@thi.ng/api': 7.2.0 '@thi.ng/checks': 2.9.11 @@ -3559,645 +8581,391 @@ packages: '@thi.ng/equiv': 1.0.45 '@thi.ng/errors': 1.3.4 '@thi.ng/random': 2.4.8 - dev: false - /@thi.ng/checks@2.9.11: - resolution: {integrity: sha512-fBvWod32w24JlJsrrOdl+tlx+UNehCORi4rHaJ7l7HH+SEhD/lYTCXOBjwu9D/ztIUjMP5Q+n8cAqI5iPhbvAQ==} + '@thi.ng/checks@2.9.11': dependencies: tslib: 2.6.2 - dev: false - /@thi.ng/compare@1.3.34: - resolution: {integrity: sha512-E+UWhmo8l5yeHDuriPUsfrnk/Mj5kSDNRX7lPfv2zNdAQ7N8UDzc0IXu46U6EpqtCReo+2n5N8qzfD3TjerFRw==} + '@thi.ng/compare@1.3.34': dependencies: '@thi.ng/api': 7.2.0 - dev: false - /@thi.ng/equiv@1.0.45: - resolution: {integrity: sha512-tdXaJfF0pFvT80Q7BOlhc7H7ja/RbVGzlGpE4LqjDWfXPPbLYwmq6EbQuHWeXuvT0qe+BsGnuO5UXAR5B8oGGQ==} - dev: false + '@thi.ng/equiv@1.0.45': {} - /@thi.ng/errors@1.3.4: - resolution: {integrity: sha512-hTk71OPKnioN349sdj2DAoY+69eSerB3MN4Zwz6mosr1QFzIMkfkNOtBeC+Gm0yi0V0EY5LeBYFgqb3oXbtTbw==} - dev: false + '@thi.ng/errors@1.3.4': {} - /@thi.ng/hex@1.0.4: - resolution: {integrity: sha512-9ofIG4nXhEskGeOJthpi/9LXFIPrlZ/MmHpgLWa3wNqTVhODP/o++mu9jDKojHEpKvswkkFCE+mSVmMu8xo4mQ==} - dev: false + '@thi.ng/hex@1.0.4': {} - /@thi.ng/random@2.4.8: - resolution: {integrity: sha512-4JJB8zbaPxjlAp1kCqsBbs6eN4Ivd/5fs1e4GlvmNkyGSucHIDTWvw6NnQWqUx2oPaAEDB9CFCH7SOcGC/cwkw==} + '@thi.ng/random@2.4.8': dependencies: '@thi.ng/api': 7.2.0 '@thi.ng/checks': 2.9.11 '@thi.ng/hex': 1.0.4 - dev: false - /@thi.ng/zipper@1.0.3: - resolution: {integrity: sha512-dWfuk5nzf5wGEmcF90AXNEuWr3NVwRF+cf/9ZSE6xImA7Vy5XpHNMwLHFszZaC+kqiDXr+EZ0lXWDF46a8lSPA==} + '@thi.ng/zipper@1.0.3': dependencies: '@thi.ng/api': 7.2.0 '@thi.ng/arrays': 1.0.3 '@thi.ng/checks': 2.9.11 - dev: false - /@tsconfig/node10@1.0.9: - resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} - dev: true + '@tsconfig/node10@1.0.11': {} - /@tsconfig/node12@1.0.11: - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - dev: true + '@tsconfig/node12@1.0.11': {} - /@tsconfig/node14@1.0.3: - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - dev: true + '@tsconfig/node14@1.0.3': {} - /@tsconfig/node16@1.0.4: - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - dev: true + '@tsconfig/node16@1.0.4': {} - /@types/auth-header@1.0.6: - resolution: {integrity: sha512-TjQyS7b+msxND/uuvza7FWSiBBLtI5y9vB55rpTeMcO2M5DSs4ony9WNKDvZLJL2w5aJH2A4C+ht1c9MPHhJWQ==} - dev: true + '@types/auth-header@1.0.6': {} - /@types/aws4@1.11.6: - resolution: {integrity: sha512-5CnVUkHNyLGpD9AnOcK66YyP0qvIh6nhJJoeK8zSl5YKikUcUbdB7SlHevUYVqicgeh6j5AJa1qa/h08dSZHoA==} + '@types/aws4@1.11.6': dependencies: - '@types/node': 18.19.21 - dev: true + '@types/node': 18.19.33 - /@types/babel__core@7.20.5: - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.23.9 - '@babel/types': 7.23.9 + '@babel/parser': 7.24.5 + '@babel/types': 7.24.5 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.5 - dev: true - /@types/babel__generator@7.6.8: - resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + '@types/babel__generator@7.6.8': dependencies: - '@babel/types': 7.23.9 - dev: true + '@babel/types': 7.24.5 - /@types/babel__template@7.4.4: - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.23.9 - '@babel/types': 7.23.9 - dev: true + '@babel/parser': 7.24.5 + '@babel/types': 7.24.5 - /@types/babel__traverse@7.20.5: - resolution: {integrity: sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==} + '@types/babel__traverse@7.20.5': dependencies: - '@babel/types': 7.23.9 - dev: true + '@babel/types': 7.24.5 - /@types/better-sqlite3@7.6.9: - resolution: {integrity: sha512-FvktcujPDj9XKMJQWFcl2vVl7OdRIqsSRX9b0acWwTmwLK9CF2eqo/FRcmMLNpugKoX/avA6pb7TorDLmpgTnQ==} + '@types/better-sqlite3@7.6.10': dependencies: - '@types/node': 18.19.21 - dev: false + '@types/node': 18.19.33 - /@types/breejs__later@4.1.5: - resolution: {integrity: sha512-O7VIO7sktsIwmLUyEeUnLMJ+QD2pv0yBGI2EMbVmwC1GOOTWJAaneL82ZyIwRgpEjJ9ciUHP8LuuuU55uj5ZjA==} - dev: true + '@types/breejs__later@4.1.5': {} - /@types/bunyan@1.8.11: - resolution: {integrity: sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ==} + '@types/bunyan@1.8.11': dependencies: - '@types/node': 18.19.21 - dev: true + '@types/node': 18.19.33 - /@types/bunyan@1.8.9: - resolution: {integrity: sha512-ZqS9JGpBxVOvsawzmVt30sP++gSQMTejCkIAQ3VdadOcRE8izTyW66hufvwLeH+YEGP6Js2AW7Gz+RMyvrEbmw==} + '@types/bunyan@1.8.9': dependencies: - '@types/node': 18.19.21 - dev: false + '@types/node': 18.19.33 - /@types/cacache@17.0.2: - resolution: {integrity: sha512-IrqHzVX2VRMDQQKa7CtKRnuoCLdRJiLW6hWU+w7i7+AaQ0Ii5bKwJxd5uRK4zBCyrHd3tG6G8zOm2LplxbSfQg==} + '@types/cacache@17.0.2': dependencies: - '@types/node': 18.19.21 - dev: true + '@types/node': 18.19.33 - /@types/cacheable-request@6.0.3: - resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} + '@types/cacheable-request@6.0.3': dependencies: '@types/http-cache-semantics': 4.0.4 '@types/keyv': 3.1.4 - '@types/node': 18.19.21 + '@types/node': 18.19.33 '@types/responselike': 1.0.3 - dev: false - /@types/callsite@1.0.34: - resolution: {integrity: sha512-eglitkkbqiQiijtKsUvOcQm+E6qLMPcggjDJXeqNBnLxdzffRGop2+2QDN/8pHh396/jN5cmIwweNKUqKJ50mQ==} - dev: true + '@types/callsite@1.0.34': {} - /@types/changelog-filename-regex@2.0.2: - resolution: {integrity: sha512-H9iuCn3Ata8075f1Nyg/WScyicJ3eXr7AklsOrPeME3sa8izDlpBhbWurtdZJfuo4Vc5+J7wNoD9Yo1d66sj+A==} - dev: true + '@types/changelog-filename-regex@2.0.2': {} - /@types/clean-git-ref@2.0.2: - resolution: {integrity: sha512-2z9rK9ayJHatZt9oDLCGE0FsArvjG1xWGuSufh6FTbbPdbpGj7cpzhfcKbVnyrwatTQ5KyxhurmGBM2xDa8Jgw==} - dev: true + '@types/clean-git-ref@2.0.2': {} - /@types/common-tags@1.8.4: - resolution: {integrity: sha512-S+1hLDJPjWNDhcGxsxEbepzaxWqURP/o+3cP4aa2w7yBXgdcmKGQtZzP8JbyfOd0m+33nh+8+kvxYE2UJtBDkg==} - dev: true + '@types/common-tags@1.8.4': {} - /@types/conventional-commits-detector@1.0.2: - resolution: {integrity: sha512-Yzo8dW+b2vziyDD9WNY+IPq4rcZyguHNuyNZC3wv0igpVFRd7VWHufl+vRQaCzDR2ftPTB1VPwbvXxWVpzBo+g==} - dev: true + '@types/conventional-commits-detector@1.0.2': {} - /@types/diff@5.0.9: - resolution: {integrity: sha512-RWVEhh/zGXpAVF/ZChwNnv7r4rvqzJ7lYNSmZSVTxjV0PBLf6Qu7RNg+SUtkpzxmiNkjCx0Xn2tPp7FIkshJwQ==} - dev: true + '@types/diff@5.2.1': {} - /@types/emscripten@1.39.10: - resolution: {integrity: sha512-TB/6hBkYQJxsZHSqyeuO1Jt0AB/bW6G7rHt9g7lML7SOF6lbgcHvw/Lr+69iqN0qxgXLhWKScAon73JNnptuDw==} - dev: false + '@types/emscripten@1.39.11': {} - /@types/eslint@8.56.3: - resolution: {integrity: sha512-PvSf1wfv2wJpVIFUMSb+i4PvqNYkB9Rkp9ZDO3oaWzq4SKhsQk4mrMBr3ZH06I0hKrVGLBacmgl8JM4WVjb9dg==} + '@types/eslint@8.56.10': dependencies: '@types/estree': 1.0.5 '@types/json-schema': 7.0.15 - dev: true - /@types/estree@1.0.5: - resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - dev: true + '@types/estree@1.0.5': {} - /@types/fs-extra@11.0.4: - resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} + '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 18.19.21 - dev: true + '@types/node': 18.19.33 - /@types/git-url-parse@9.0.3: - resolution: {integrity: sha512-Wrb8zeghhpKbYuqAOg203g+9YSNlrZWNZYvwxJuDF4dTmerijqpnGbI79yCuPtHSXHPEwv1pAFUB4zsSqn82Og==} - dev: true + '@types/git-url-parse@9.0.3': {} - /@types/github-url-from-git@1.5.3: - resolution: {integrity: sha512-0vnjtdEpqLTRBlgkzXsRaAQ0T8Nx48fW7qWl/Y5a4MTXEL2mXFV8rNPiFPCYrJFPOeyUJRzNzcs91MgJd+fFSA==} - dev: true + '@types/github-url-from-git@1.5.3': {} - /@types/global-agent@2.1.3: - resolution: {integrity: sha512-rGtZZcgZcKWuKNTkGBGsqyOQ7Nn2MjXh4+xeZbf+5b5KMUx8H1rTqLRackxos7pUlreszbYjQcop5JvqCnZlLw==} - dev: true + '@types/global-agent@2.1.3': {} - /@types/graceful-fs@4.1.9: - resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 18.19.21 - dev: true + '@types/node': 18.19.33 - /@types/http-cache-semantics@4.0.4: - resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} - dev: false + '@types/http-cache-semantics@4.0.4': {} - /@types/ini@1.3.34: - resolution: {integrity: sha512-FafeLhwmWucTi31ZYg/6aHBZNyrogQ35aDvSW7zMAz3HMhUqQ4G/NBya8c5pe2jwoYsDFwra8O9/yZotong76g==} - dev: true + '@types/ini@4.1.0': {} - /@types/istanbul-lib-coverage@2.0.6: - resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} - dev: true + '@types/istanbul-lib-coverage@2.0.6': {} - /@types/istanbul-lib-report@3.0.3: - resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + '@types/istanbul-lib-report@3.0.3': dependencies: '@types/istanbul-lib-coverage': 2.0.6 - dev: true - /@types/istanbul-reports@3.0.4: - resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + '@types/istanbul-reports@3.0.4': dependencies: '@types/istanbul-lib-report': 3.0.3 - dev: true - /@types/js-yaml@4.0.9: - resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} - dev: true + '@types/js-yaml@4.0.9': {} - /@types/json-dup-key-validator@1.0.2: - resolution: {integrity: sha512-zJSAGITlz2nFT7xcKsvns8UifwSJpKuhgsdZj7+WoxiixiGnIefNiLK2uNhEICRkI9S2ccU6RYdqPS7iJRtU7Q==} - dev: true + '@types/json-dup-key-validator@1.0.2': {} - /@types/json-schema@7.0.15: - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - dev: true + '@types/json-schema@7.0.15': {} - /@types/json5@0.0.29: - resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - dev: true + '@types/json5@0.0.29': {} - /@types/jsonfile@6.1.4: - resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} + '@types/jsonfile@6.1.4': dependencies: - '@types/node': 18.19.21 - dev: true + '@types/node': 18.19.33 - /@types/keyv@3.1.4: - resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} + '@types/keyv@3.1.4': dependencies: - '@types/node': 18.19.21 - dev: false + '@types/node': 18.19.33 - /@types/linkify-it@3.0.5: - resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==} - dev: true + '@types/linkify-it@3.0.5': {} - /@types/linkify-markdown@1.0.3: - resolution: {integrity: sha512-BnuGqDmpzmXCDMXHzgle/vMRUnbFcWclts0+n7Or421exav3XG6efl9gsxamLET6QPhX+pMnxcsHgnAO/daj9w==} - dev: true + '@types/linkify-markdown@1.0.3': {} - /@types/lodash@4.14.202: - resolution: {integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==} - dev: true + '@types/lodash@4.17.1': {} - /@types/luxon@3.4.2: - resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==} - dev: true + '@types/luxon@3.4.2': {} - /@types/markdown-it@13.0.7: - resolution: {integrity: sha512-U/CBi2YUUcTHBt5tjO2r5QV/x0Po6nsYwQU4Y04fBS6vfoImaiZ6f8bi3CjTCxBPQSO1LMyUqkByzi8AidyxfA==} + '@types/markdown-it@14.0.1': dependencies: '@types/linkify-it': 3.0.5 - '@types/mdurl': 1.0.5 - dev: true + '@types/mdurl': 2.0.0 - /@types/markdown-table@2.0.0: - resolution: {integrity: sha512-fVZN/DRjZvjuk+lo7ovlI/ZycS51gpYU5vw5EcFeqkcX6lucQ+UWgEOH2O4KJHkSck4DHAY7D7CkVLD0wzc5qw==} - dev: true + '@types/markdown-table@2.0.0': {} - /@types/marshal@0.5.3: - resolution: {integrity: sha512-ptxKIirn/lt95Zi/MErrtn/K8VvrByNOAF9gxbIJCxWj9CXAifjAvm/bRMg7WXQjwi1DlbXG6HJ1RzHe6oYEug==} + '@types/marshal@0.5.3': dependencies: - '@types/node': 18.19.21 - dev: true + '@types/node': 18.19.33 - /@types/mdast@3.0.15: - resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} + '@types/mdast@3.0.15': dependencies: '@types/unist': 2.0.10 - /@types/mdurl@1.0.5: - resolution: {integrity: sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==} - dev: true - - /@types/minimist@1.2.5: - resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} - dev: false + '@types/mdurl@2.0.0': {} - /@types/moo@0.5.5: - resolution: {integrity: sha512-eXQpwnkI4Ntw5uJg6i2PINdRFWLr55dqjuYQaLHNjvqTzF14QdNWbCbml9sza0byyXNA0hZlHtcdN+VNDcgVHA==} - dev: false + '@types/minimist@1.2.5': {} - /@types/moo@0.5.9: - resolution: {integrity: sha512-ZsFVecFi66jGQ6L41TonEaBhsIVeVftTz6iQKWTctzacHhzYHWvv9S0IyAJi4BhN7vb9qCQ3+kpStP2vbZqmDg==} - dev: true + '@types/moo@0.5.5': {} - /@types/ms@0.7.34: - resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - dev: false + '@types/moo@0.5.9': {} - /@types/nock@10.0.3: - resolution: {integrity: sha512-OthuN+2FuzfZO3yONJ/QVjKmLEuRagS9TV9lEId+WHL9KhftYG+/2z+pxlr0UgVVXSpVD8woie/3fzQn8ft/Ow==} - dependencies: - '@types/node': 18.19.21 - dev: true + '@types/ms@0.7.34': {} - /@types/node@18.19.21: - resolution: {integrity: sha512-2Q2NeB6BmiTFQi4DHBzncSoq/cJMLDdhPaAoJFnFCyD9a8VPZRf7a1GAwp1Edb7ROaZc5Jz/tnZyL6EsWMRaqw==} + '@types/node@18.19.33': dependencies: undici-types: 5.26.5 - /@types/normalize-package-data@2.4.4: - resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - - /@types/parse-link-header@2.0.3: - resolution: {integrity: sha512-ffLAxD6Xqcf2gSbtEJehj8yJ5R/2OZqD4liodQvQQ+hhO4kg1mk9ToEZQPMtNTm/zIQj2GNleQbsjPp9+UQm4Q==} - dev: true + '@types/normalize-package-data@2.4.4': {} - /@types/prettier@2.7.3: - resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==} - dev: true + '@types/parse-link-header@2.0.3': {} - /@types/responselike@1.0.3: - resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} + '@types/responselike@1.0.3': dependencies: - '@types/node': 18.19.21 - dev: false + '@types/node': 18.19.33 - /@types/semver-stable@3.0.2: - resolution: {integrity: sha512-uNLK57+EY0r8VprVwHytHhlTb1tUVZiWgXkMBKoeu1/3LaFq+ZiaG29xAC3APAWG7xdedwGqeUY8N1y9YG1vjw==} - dev: true + '@types/semver-stable@3.0.2': {} - /@types/semver-utils@1.1.3: - resolution: {integrity: sha512-T+YwkslhsM+CeuhYUxyAjWm7mJ5am/K10UX40RuA6k6Lc7eGtq8iY2xOzy7Vq0GOqhl/xZl5l2FwURZMTPTUww==} - dev: true + '@types/semver-utils@1.1.3': {} - /@types/semver@7.5.6: - resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} + '@types/semver@7.5.8': {} - /@types/shimmer@1.0.5: - resolution: {integrity: sha512-9Hp0ObzwwO57DpLFF0InUjUm/II8GmKAvzbefxQTihCb7KI6yc9yzf0nLc4mVdby5N4DRCgQM2wCup9KTieeww==} - dev: false + '@types/shimmer@1.0.5': {} - /@types/sinon@10.0.20: - resolution: {integrity: sha512-2APKKruFNCAZgx3daAyACGzWuJ028VVCUDk6o2rw/Z4PXT0ogwdV4KUegW0MwVs0Zu59auPXbbuBJHF12Sx1Eg==} + '@types/sinon@10.0.20': dependencies: '@types/sinonjs__fake-timers': 8.1.5 - dev: true - /@types/sinonjs__fake-timers@8.1.5: - resolution: {integrity: sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==} - dev: true + '@types/sinonjs__fake-timers@8.1.5': {} - /@types/stack-utils@2.0.3: - resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - dev: true + '@types/stack-utils@2.0.3': {} - /@types/tar@6.1.11: - resolution: {integrity: sha512-ThA1WD8aDdVU4VLuyq5NEqriwXErF5gEIJeyT6gHBWU7JtSmW2a5qjNv3/vR82O20mW+1vhmeZJfBQPT3HCugg==} + '@types/tar@6.1.13': dependencies: - '@types/node': 18.19.21 + '@types/node': 18.19.33 minipass: 4.2.8 - dev: true - /@types/tmp@0.2.6: - resolution: {integrity: sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==} - dev: false + '@types/tmp@0.2.6': {} - /@types/traverse@0.6.36: - resolution: {integrity: sha512-0ko4fZgKQf2J/2FvDJHua9KZ19zJkfI5FxG1ZeEUhezEwuq6UL+4T0IxcBfmHilBeAj7OSUTwrm/lPnh8EB1/Q==} - dev: true + '@types/traverse@0.6.36': {} - /@types/treeify@1.0.3: - resolution: {integrity: sha512-hx0o7zWEUU4R2Amn+pjCBQQt23Khy/Dk56gQU5xi5jtPL1h83ACJCeFaB2M/+WO1AntvWrSoVnnCAfI1AQH4Cg==} - dev: false + '@types/treeify@1.0.3': {} - /@types/unist@2.0.10: - resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} + '@types/unist@2.0.10': {} - /@types/url-join@4.0.3: - resolution: {integrity: sha512-3l1qMm3wqO0iyC5gkADzT95UVW7C/XXcdvUcShOideKF0ddgVRErEQQJXBd2kvQm+aSgqhBGHGB38TgMeT57Ww==} - dev: true + '@types/url-join@4.0.3': {} - /@types/validate-npm-package-name@4.0.2: - resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==} - dev: true + '@types/validate-npm-package-name@4.0.2': {} - /@types/xmldoc@1.1.9: - resolution: {integrity: sha512-HLwIAudQ9xedPOK9rKd7gSHYzM5qtWOzae9z5tM7dRDR1hWeNlFSejfnxFMIv06mm2LmtX+pzVQ4GN86vf/b3g==} - dev: true + '@types/xmldoc@1.1.9': {} - /@types/yargs-parser@21.0.3: - resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - dev: true + '@types/yargs-parser@21.0.3': {} - /@types/yargs@17.0.32: - resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==} + '@types/yargs@17.0.32': dependencies: '@types/yargs-parser': 21.0.3 - dev: true - /@types/yauzl@2.10.3: - resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - requiresBuild: true + '@types/yauzl@2.10.3': dependencies: - '@types/node': 18.19.21 - dev: false + '@types/node': 18.19.33 optional: true - /@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/eslint-plugin@7.10.0(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': dependencies: '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3) - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/type-utils': 6.21.0(eslint@8.56.0)(typescript@5.3.3) - '@typescript-eslint/utils': 6.21.0(eslint@8.56.0)(typescript@5.3.3) - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.4 - eslint: 8.56.0 + '@typescript-eslint/parser': 7.10.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/scope-manager': 7.10.0 + '@typescript-eslint/type-utils': 7.10.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/utils': 7.10.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 7.10.0 + eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.1 natural-compare: 1.4.0 - semver: 7.5.4 - ts-api-utils: 1.2.1(typescript@5.3.3) - typescript: 5.3.3 + ts-api-utils: 1.3.0(typescript@5.4.5) + optionalDependencies: + typescript: 5.4.5 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/experimental-utils@5.62.0(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-RTXpeB3eMkpoclG3ZHft6vG/Z30azNHuqY6wKPBHlVMZFuEvrtlEDe8gMqDb+SO+9hjC/pLekeSCryf9vMZlCw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + '@typescript-eslint/experimental-utils@5.62.0(eslint@8.57.0)(typescript@5.4.5)': dependencies: - '@typescript-eslint/utils': 5.62.0(eslint@8.56.0)(typescript@5.3.3) - eslint: 8.56.0 + '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.4.5) + eslint: 8.57.0 transitivePeerDependencies: - supports-color - typescript - dev: true - /@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5)': dependencies: - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) - '@typescript-eslint/visitor-keys': 6.21.0 + '@typescript-eslint/scope-manager': 7.10.0 + '@typescript-eslint/types': 7.10.0 + '@typescript-eslint/typescript-estree': 7.10.0(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 7.10.0 debug: 4.3.4 - eslint: 8.56.0 - typescript: 5.3.3 + eslint: 8.57.0 + optionalDependencies: + typescript: 5.4.5 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/scope-manager@5.62.0: - resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@typescript-eslint/scope-manager@5.62.0': dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - dev: true - /@typescript-eslint/scope-manager@6.21.0: - resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/scope-manager@7.10.0': dependencies: - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/visitor-keys': 6.21.0 - dev: true + '@typescript-eslint/types': 7.10.0 + '@typescript-eslint/visitor-keys': 7.10.0 - /@typescript-eslint/type-utils@6.21.0(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/type-utils@7.10.0(eslint@8.57.0)(typescript@5.4.5)': dependencies: - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) - '@typescript-eslint/utils': 6.21.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/typescript-estree': 7.10.0(typescript@5.4.5) + '@typescript-eslint/utils': 7.10.0(eslint@8.57.0)(typescript@5.4.5) debug: 4.3.4 - eslint: 8.56.0 - ts-api-utils: 1.2.1(typescript@5.3.3) - typescript: 5.3.3 + eslint: 8.57.0 + ts-api-utils: 1.3.0(typescript@5.4.5) + optionalDependencies: + typescript: 5.4.5 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/types@5.62.0: - resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true + '@typescript-eslint/types@5.62.0': {} - /@typescript-eslint/types@6.21.0: - resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} - engines: {node: ^16.0.0 || >=18.0.0} - dev: true + '@typescript-eslint/types@7.10.0': {} - /@typescript-eslint/typescript-estree@5.62.0(typescript@5.3.3): - resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/typescript-estree@5.62.0(typescript@5.4.5)': 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.5.4 - tsutils: 3.21.0(typescript@5.3.3) - typescript: 5.3.3 + semver: 7.6.2 + tsutils: 3.21.0(typescript@5.4.5) + optionalDependencies: + typescript: 5.4.5 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/typescript-estree@6.21.0(typescript@5.3.3): - resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/typescript-estree@7.10.0(typescript@5.4.5)': dependencies: - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/visitor-keys': 6.21.0 + '@typescript-eslint/types': 7.10.0 + '@typescript-eslint/visitor-keys': 7.10.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 - minimatch: 9.0.3 - semver: 7.5.4 - ts-api-utils: 1.2.1(typescript@5.3.3) - typescript: 5.3.3 + minimatch: 9.0.4 + semver: 7.6.2 + ts-api-utils: 1.3.0(typescript@5.4.5) + optionalDependencies: + typescript: 5.4.5 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/utils@5.62.0(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + '@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.4.5)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@types/json-schema': 7.0.15 - '@types/semver': 7.5.6 + '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.3) - eslint: 8.56.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.4.5) + eslint: 8.57.0 eslint-scope: 5.1.1 - semver: 7.5.4 + semver: 7.6.2 transitivePeerDependencies: - supports-color - typescript - dev: true - /@typescript-eslint/utils@6.21.0(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 + '@typescript-eslint/utils@7.10.0(eslint@8.57.0)(typescript@5.4.5)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) - '@types/json-schema': 7.0.15 - '@types/semver': 7.5.6 - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) - eslint: 8.56.0 - semver: 7.5.4 + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@typescript-eslint/scope-manager': 7.10.0 + '@typescript-eslint/types': 7.10.0 + '@typescript-eslint/typescript-estree': 7.10.0(typescript@5.4.5) + eslint: 8.57.0 transitivePeerDependencies: - supports-color - typescript - dev: true - /@typescript-eslint/visitor-keys@5.62.0: - resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@typescript-eslint/visitor-keys@5.62.0': dependencies: '@typescript-eslint/types': 5.62.0 eslint-visitor-keys: 3.4.3 - dev: true - /@typescript-eslint/visitor-keys@6.21.0: - resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/visitor-keys@7.10.0': dependencies: - '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/types': 7.10.0 eslint-visitor-keys: 3.4.3 - dev: true - /@ungap/structured-clone@1.2.0: - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - dev: true + '@ungap/structured-clone@1.2.0': {} - /@yarnpkg/core@4.0.3(typanion@3.14.0): - resolution: {integrity: sha512-O+WGCjB9aIBxdRMBxXdsIy08MW4RbxfCS2AfywWb8DPS9H0LICahUJgNAaE0fwCsW7/gNzQbLYlh9DQQwzONrA==} - engines: {node: '>=18.12.0'} + '@yarnpkg/core@4.0.5(typanion@3.14.0)': dependencies: '@arcanis/slice-ansi': 1.1.1 - '@types/semver': 7.5.6 + '@types/semver': 7.5.8 '@types/treeify': 1.0.3 '@yarnpkg/fslib': 3.0.2 '@yarnpkg/libzip': 3.0.1(@yarnpkg/fslib@3.0.2) - '@yarnpkg/parsers': 3.0.0 - '@yarnpkg/shell': 4.0.0(typanion@3.14.0) + '@yarnpkg/parsers': 3.0.2 + '@yarnpkg/shell': 4.0.2(typanion@3.14.0) camelcase: 5.3.1 chalk: 3.0.0 ci-info: 3.9.0 @@ -4210,50 +8978,35 @@ packages: lodash: 4.17.21 micromatch: 4.0.5 p-limit: 2.3.0 - semver: 7.5.4 + semver: 7.6.2 strip-ansi: 6.0.1 - tar: 6.2.0 + tar: 6.2.1 tinylogic: 2.0.0 treeify: 1.1.0 tslib: 2.6.2 tunnel: 0.0.6 transitivePeerDependencies: - typanion - dev: false - /@yarnpkg/fslib@3.0.2: - resolution: {integrity: sha512-dqSpZCj9ZeqMZzITHvpi5qKS9MCLWZYCl6NHCSl3a+BqiyLApehWAy60zjv9F1fxKZ6RKDgHLhBWhx8RwPm6Dw==} - engines: {node: '>=18.12.0'} + '@yarnpkg/fslib@3.0.2': dependencies: tslib: 2.6.2 - dev: false - /@yarnpkg/libzip@3.0.1(@yarnpkg/fslib@3.0.2): - resolution: {integrity: sha512-fiqRLk2fyd2r34/Qc7HlP8fKIo1CK5CZpLNObJwnbFmZQN2hVanovFlG++3oH3qYJymEmjPl5EGsygcEJZl4Pg==} - engines: {node: '>=18.12.0'} - peerDependencies: - '@yarnpkg/fslib': ^3.0.2 + '@yarnpkg/libzip@3.0.1(@yarnpkg/fslib@3.0.2)': dependencies: - '@types/emscripten': 1.39.10 + '@types/emscripten': 1.39.11 '@yarnpkg/fslib': 3.0.2 tslib: 2.6.2 - dev: false - /@yarnpkg/parsers@3.0.0: - resolution: {integrity: sha512-jVZa3njBv6tcOUw34nlUdUM/40wwtm/gnVF8rtk0tA6vNcokqYI8CFU1BZjlpFwUSZaXxYkrtuPE/f2MMFlTxQ==} - engines: {node: '>=18.12.0'} + '@yarnpkg/parsers@3.0.2': dependencies: js-yaml: 3.14.1 tslib: 2.6.2 - dev: false - /@yarnpkg/shell@4.0.0(typanion@3.14.0): - resolution: {integrity: sha512-Yk2gyiQvsoee/jXP9q0jMl412Nx27LYu+P1O4DHuxeutL9qtd6t3Ktuv+zZmOzFc6gMQ7+/6mQFPo3/LlXZM3w==} - engines: {node: '>=18.12.0'} - hasBin: true + '@yarnpkg/shell@4.0.2(typanion@3.14.0)': dependencies: '@yarnpkg/fslib': 3.0.2 - '@yarnpkg/parsers': 3.0.0 + '@yarnpkg/parsers': 3.0.2 chalk: 3.0.0 clipanion: 4.0.0-rc.3(typanion@3.14.0) cross-spawn: 7.0.3 @@ -4262,539 +9015,344 @@ packages: tslib: 2.6.2 transitivePeerDependencies: - typanion - dev: false - /JSONStream@1.3.5: - resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} - hasBin: true + JSONStream@1.3.5: dependencies: jsonparse: 1.3.1 through: 2.3.8 - dev: true - /abbrev@2.0.0: - resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - requiresBuild: true - dev: false + abbrev@2.0.0: optional: true - /acorn-import-assertions@1.9.0(acorn@8.11.3): - resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} - peerDependencies: - acorn: ^8 + acorn-import-attributes@1.9.5(acorn@8.11.3): dependencies: acorn: 8.11.3 - dev: false - /acorn-jsx@5.3.2(acorn@8.11.3): - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn-jsx@5.3.2(acorn@8.11.3): dependencies: acorn: 8.11.3 - dev: true - /acorn-walk@8.3.2: - resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} - engines: {node: '>=0.4.0'} - dev: true + acorn-walk@8.3.2: {} - /acorn@8.11.3: - resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} - engines: {node: '>=0.4.0'} - hasBin: true + acorn@8.11.3: {} - /adm-zip@0.5.10: - resolution: {integrity: sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==} - engines: {node: '>=6.0'} - dev: false + adm-zip@0.5.12: {} - /agent-base@7.1.0: - resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==} - engines: {node: '>= 14'} + agent-base@7.1.1: dependencies: debug: 4.3.4 transitivePeerDependencies: - supports-color - /agentkeepalive@4.5.0: - resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} - engines: {node: '>= 8.0.0'} + agentkeepalive@4.5.0: dependencies: humanize-ms: 1.2.1 - dev: false - /aggregate-error@3.1.0: - resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} - engines: {node: '>=8'} + aggregate-error@3.1.0: dependencies: clean-stack: 2.2.0 indent-string: 4.0.0 - /aggregate-error@5.0.0: - resolution: {integrity: sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==} - engines: {node: '>=18'} + aggregate-error@5.0.0: dependencies: clean-stack: 5.2.0 indent-string: 5.0.0 - dev: true - /ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 json-schema-traverse: 0.4.1 uri-js: 4.4.1 - dev: true - /ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} + ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 - dev: true - /ansi-escapes@6.2.0: - resolution: {integrity: sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==} - engines: {node: '>=14.16'} - dependencies: - type-fest: 3.13.1 - dev: true + ansi-escapes@6.2.1: {} - /ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} + ansi-regex@5.0.1: {} - /ansi-regex@6.0.1: - resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} - engines: {node: '>=12'} + ansi-regex@6.0.1: {} - /ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} + ansi-styles@3.2.1: dependencies: color-convert: 1.9.3 - /ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 - /ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - dev: true + ansi-styles@5.2.0: {} - /ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} + ansi-styles@6.2.1: {} - /ansicolors@0.3.2: - resolution: {integrity: sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==} - dev: true + ansicolors@0.3.2: {} - /anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 - dev: true - /append-transform@2.0.0: - resolution: {integrity: sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==} - engines: {node: '>=8'} + append-transform@2.0.0: dependencies: default-require-extensions: 3.0.1 - dev: true - /archy@1.0.0: - resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==} - dev: true + archy@1.0.0: {} - /arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - dev: true + arg@4.1.3: {} - /argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 - /argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + argparse@2.0.1: {} - /argv-formatter@1.0.0: - resolution: {integrity: sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==} - dev: true + argv-formatter@1.0.0: {} - /array-buffer-byte-length@1.0.1: - resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} - engines: {node: '>= 0.4'} + array-buffer-byte-length@1.0.1: dependencies: call-bind: 1.0.7 is-array-buffer: 3.0.4 - dev: true - /array-ify@1.0.0: - resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} - dev: true + array-ify@1.0.0: {} - /array-includes@3.1.7: - resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==} - engines: {node: '>= 0.4'} + array-includes@3.1.8: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.4 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 get-intrinsic: 1.2.4 is-string: 1.0.7 - dev: true - /array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - dev: true - - /array.prototype.filter@1.0.3: - resolution: {integrity: sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.22.4 - es-array-method-boxes-properly: 1.0.0 - is-string: 1.0.7 - dev: true + array-union@2.1.0: {} - /array.prototype.findlastindex@1.2.4: - resolution: {integrity: sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==} - engines: {node: '>= 0.4'} + array.prototype.findlastindex@1.2.5: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.4 + es-abstract: 1.23.3 es-errors: 1.3.0 + es-object-atoms: 1.0.0 es-shim-unscopables: 1.0.2 - dev: true - - /array.prototype.flat@1.3.2: - resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} - engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.2: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.4 + es-abstract: 1.23.3 es-shim-unscopables: 1.0.2 - dev: true - /array.prototype.flatmap@1.3.2: - resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} - engines: {node: '>= 0.4'} + array.prototype.flatmap@1.3.2: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.4 + es-abstract: 1.23.3 es-shim-unscopables: 1.0.2 - dev: true - /arraybuffer.prototype.slice@1.0.3: - resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} - engines: {node: '>= 0.4'} + arraybuffer.prototype.slice@1.0.3: dependencies: array-buffer-byte-length: 1.0.1 call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.4 + es-abstract: 1.23.3 es-errors: 1.3.0 get-intrinsic: 1.2.4 is-array-buffer: 3.0.4 is-shared-array-buffer: 1.0.3 - dev: true - /arrify@1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} - dev: false + arrify@1.0.1: {} - /asn1.js@5.4.1: - resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} + asn1.js@5.4.1: dependencies: bn.js: 4.12.0 inherits: 2.0.4 minimalistic-assert: 1.0.1 safer-buffer: 2.1.2 - dev: false + optional: true - /auth-header@1.0.0: - resolution: {integrity: sha512-CPPazq09YVDUNNVWo4oSPTQmtwIzHusZhQmahCKvIsk0/xH6U3QsMAv3sM+7+Q0B1K2KJ/Q38OND317uXs4NHA==} - dev: false + auth-header@1.0.0: {} - /available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.0.0 - /aws-sdk-client-mock@3.0.1: - resolution: {integrity: sha512-9VAzJLl8mz99KP9HjOm/93d8vznRRUTpJooPBOunRdUAnVYopCe9xmMuu7eVemu8fQ+w6rP7o5bBK1kAFkB2KQ==} + aws-sdk-client-mock@4.0.0: dependencies: '@types/sinon': 10.0.20 sinon: 16.1.3 tslib: 2.6.2 - dev: true - /aws4@1.12.0: - resolution: {integrity: sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==} - dev: false + aws4@1.12.0: {} - /azure-devops-node-api@12.4.0: - resolution: {integrity: sha512-ZrJlnoAOjliBYvO1wV9oa5Saa3h5tfRbvCSpwjqryag7bIeeY5Zl/zGiZBVD+75EumhtY5mOXNBzHvLf6JmdNQ==} + azure-devops-node-api@13.0.0: dependencies: tunnel: 0.0.6 typed-rest-client: 1.8.11 - dev: false - /babel-jest@29.7.0(@babel/core@7.23.9): - resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.8.0 + babel-jest@29.7.0(@babel/core@7.24.5): dependencies: - '@babel/core': 7.23.9 + '@babel/core': 7.24.5 '@jest/transform': 29.7.0 '@types/babel__core': 7.20.5 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.23.9) + babel-preset-jest: 29.6.3(@babel/core@7.24.5) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 transitivePeerDependencies: - supports-color - dev: true - /babel-plugin-istanbul@6.1.1: - resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} - engines: {node: '>=8'} + babel-plugin-istanbul@6.1.1: dependencies: - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-plugin-utils': 7.24.5 '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-instrument: 5.2.1 test-exclude: 6.0.0 transitivePeerDependencies: - supports-color - dev: true - /babel-plugin-jest-hoist@29.6.3: - resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + babel-plugin-jest-hoist@29.6.3: dependencies: - '@babel/template': 7.23.9 - '@babel/types': 7.23.9 + '@babel/template': 7.24.0 + '@babel/types': 7.24.5 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.5 - dev: true - /babel-preset-current-node-syntax@1.0.1(@babel/core@7.23.9): - resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.23.9 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.9) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.23.9) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.23.9) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.23.9) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.9) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.9) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.9) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.9) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.9) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.9) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.9) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.23.9) - dev: true - - /babel-preset-jest@29.6.3(@babel/core@7.23.9): - resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.23.9 + babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.5): + dependencies: + '@babel/core': 7.24.5 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.5) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.24.5) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.5) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.5) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.5) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.5) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.5) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.5) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.5) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.5) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.5) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.5) + + babel-preset-jest@29.6.3(@babel/core@7.24.5): + dependencies: + '@babel/core': 7.24.5 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.9) - dev: true + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.5) - /backslash@0.2.0: - resolution: {integrity: sha512-Avs+8FUZ1HF/VFP4YWwHQZSGzRPm37ukU1JQYQWijuHhtXdOuAzcZ8PcAzfIw898a8PyBzdn+RtnKA6MzW0X2A==} - dev: false + backslash@0.2.0: {} - /bail@1.0.5: - resolution: {integrity: sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==} + bail@1.0.5: {} - /balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@1.0.2: {} - /base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: false + base64-js@1.5.1: {} - /before-after-hook@2.2.3: - resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} + before-after-hook@2.2.3: {} - /better-sqlite3@9.4.3: - resolution: {integrity: sha512-ud0bTmD9O3uWJGuXDltyj3R47Nz0OHX8iqPOT5PMspGqlu/qQFn+5S2eFBUCrySpavTjFXbi4EgrfVvPAHlImw==} - requiresBuild: true + better-sqlite3@9.6.0: dependencies: bindings: 1.5.0 - prebuild-install: 7.1.1 - dev: false + prebuild-install: 7.1.2 + optional: true - /bignumber.js@9.1.2: - resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==} - dev: false + bignumber.js@9.1.2: {} - /bindings@1.5.0: - resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + bindings@1.5.0: dependencies: file-uri-to-path: 1.0.0 - dev: false + optional: true - /bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + bl@4.1.0: dependencies: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.2 - dev: false + optional: true - /bn.js@4.12.0: - resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} - dev: false + bn.js@4.12.0: + optional: true - /boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - dev: false + bn@1.0.5: {} - /boolean@3.2.0: - resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} - dev: false + boolbase@1.0.0: {} - /bottleneck@2.19.5: - resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} - dev: true + boolean@3.2.0: {} - /bowser@2.11.0: - resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} - dev: false + bottleneck@2.19.5: {} - /brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + bowser@2.11.0: {} + + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - /brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@2.0.1: dependencies: balanced-match: 1.0.2 - /braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} + braces@3.0.2: dependencies: fill-range: 7.0.1 - /browserslist@4.23.0: - resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true + browserslist@4.23.0: dependencies: - caniuse-lite: 1.0.30001589 - electron-to-chromium: 1.4.681 + caniuse-lite: 1.0.30001616 + electron-to-chromium: 1.4.756 node-releases: 2.0.14 - update-browserslist-db: 1.0.13(browserslist@4.23.0) - dev: true + update-browserslist-db: 1.0.15(browserslist@4.23.0) - /bs-logger@0.2.6: - resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} - engines: {node: '>= 6'} + bs-logger@0.2.6: dependencies: fast-json-stable-stringify: 2.1.0 - dev: true - /bser@2.1.1: - resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + bser@2.1.1: dependencies: node-int64: 0.4.0 - dev: true - /buffer-crc32@0.2.13: - resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} - dev: false + buffer-crc32@0.2.13: {} - /buffer-equal-constant-time@1.0.1: - resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} - dev: false + buffer-equal-constant-time@1.0.1: {} - /buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer-from@1.1.2: {} - /buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + buffer@5.7.1: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - dev: false - - /builtins@5.0.1: - resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} - dependencies: - semver: 7.5.4 - dev: false + optional: true - /bunyan@1.8.15: - resolution: {integrity: sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==} - engines: {'0': node >=0.10.0} - hasBin: true + bunyan@1.8.15: optionalDependencies: dtrace-provider: 0.8.8 moment: 2.30.1 mv: 2.1.1 safe-json-stringify: 1.2.0 - dev: false - /cacache@18.0.2: - resolution: {integrity: sha512-r3NU8h/P+4lVUHfeRw1dtgQYar3DZMm4/cm2bZgOvrFC/su7budSOeqh52VJIC4U4iG1WWwV6vRW0znqBvxNuw==} - engines: {node: ^16.14.0 || >=18.0.0} + bzip-deflate@1.0.0: {} + + cacache@18.0.3: dependencies: '@npmcli/fs': 3.1.0 fs-minipass: 3.0.3 - glob: 10.3.10 - lru-cache: 10.2.0 - minipass: 7.0.4 + glob: 10.3.15 + lru-cache: 10.2.2 + minipass: 7.1.0 minipass-collect: 2.0.1 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 p-map: 4.0.0 - ssri: 10.0.5 - tar: 6.2.0 + ssri: 10.0.6 + tar: 6.2.1 unique-filename: 3.0.0 - dev: false - /cacheable-lookup@5.0.4: - resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} - engines: {node: '>=10.6.0'} - dev: false + cacheable-lookup@5.0.4: {} - /cacheable-request@7.0.4: - resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} - engines: {node: '>=8'} + cacheable-request@7.0.4: dependencies: clone-response: 1.0.3 get-stream: 5.2.0 @@ -4803,349 +9361,209 @@ packages: lowercase-keys: 2.0.0 normalize-url: 6.1.0 responselike: 2.0.1 - dev: false - /caching-transform@4.0.0: - resolution: {integrity: sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==} - engines: {node: '>=8'} + caching-transform@4.0.0: dependencies: hasha: 5.2.2 make-dir: 3.1.0 package-hash: 4.0.0 write-file-atomic: 3.0.3 - dev: true - /call-bind@1.0.7: - resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} - engines: {node: '>= 0.4'} + call-bind@1.0.7: dependencies: es-define-property: 1.0.0 es-errors: 1.3.0 function-bind: 1.1.2 get-intrinsic: 1.2.4 - set-function-length: 1.2.1 + set-function-length: 1.2.2 - /callsite@1.0.0: - resolution: {integrity: sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==} - dev: true + callsite@1.0.0: {} - /callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - dev: true + callsites@3.1.0: {} - /camelcase-keys@6.2.2: - resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} - engines: {node: '>=8'} + camelcase-keys@6.2.2: dependencies: camelcase: 5.3.1 map-obj: 4.3.0 quick-lru: 4.0.1 - dev: false - /camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} + camelcase@5.3.1: {} - /camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - dev: true + camelcase@6.3.0: {} - /caniuse-lite@1.0.30001589: - resolution: {integrity: sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg==} - dev: true + caniuse-lite@1.0.30001616: {} - /cardinal@2.1.1: - resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} - hasBin: true + cardinal@2.1.1: dependencies: ansicolors: 0.3.2 redeyed: 2.1.1 - dev: true - /chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 escape-string-regexp: 1.0.5 supports-color: 5.5.0 - /chalk@3.0.0: - resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} - engines: {node: '>=8'} + chalk@3.0.0: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - dev: false - /chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - /chalk@5.3.0: - resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - dev: true + chalk@5.3.0: {} - /changelog-filename-regex@2.0.1: - resolution: {integrity: sha512-DZdyJpCprw8V3jp8V2x13nAA05Yy/IN+Prowj+0mrAHNENYkuMtNI4u5m449TTjPqShIslQSEuXee+Jtkn4m+g==} - dev: false + changelog-filename-regex@2.0.1: {} - /char-regex@1.0.2: - resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} - engines: {node: '>=10'} - dev: true + char-regex@1.0.2: {} - /character-entities-legacy@1.1.4: - resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} - dev: false + character-entities-legacy@1.1.4: {} - /character-entities@1.2.4: - resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==} - dev: false + character-entities@1.2.4: {} - /character-reference-invalid@1.1.4: - resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} - dev: false + character-reference-invalid@1.1.4: {} - /chownr@1.1.4: - resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - dev: false + chownr@1.1.4: + optional: true - /chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} + chownr@2.0.0: {} - /ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} - engines: {node: '>=8'} + ci-info@3.9.0: {} - /cjs-module-lexer@1.2.3: - resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} + cjs-module-lexer@1.3.1: {} - /clean-git-ref@2.0.1: - resolution: {integrity: sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw==} - dev: false + clean-git-ref@2.0.1: {} - /clean-stack@2.2.0: - resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} - engines: {node: '>=6'} + clean-stack@2.2.0: {} - /clean-stack@5.2.0: - resolution: {integrity: sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==} - engines: {node: '>=14.16'} + clean-stack@5.2.0: dependencies: escape-string-regexp: 5.0.0 - dev: true - /cli-table3@0.6.3: - resolution: {integrity: sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==} - engines: {node: 10.* || >= 12.*} + cli-table3@0.6.4: dependencies: string-width: 4.2.3 optionalDependencies: '@colors/colors': 1.5.0 - dev: true - /clipanion@4.0.0-rc.3(typanion@3.14.0): - resolution: {integrity: sha512-+rJOJMt2N6Oikgtfqmo/Duvme7uz3SIedL2b6ycgCztQMiTfr3aQh2DDyLHl+QUPClKMNpSg3gDJFvNQYIcq1g==} - peerDependencies: - typanion: '*' + clipanion@4.0.0-rc.3(typanion@3.14.0): dependencies: typanion: 3.14.0 - dev: false - /cliui@6.0.0: - resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + cliui@6.0.0: dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 6.2.0 - dev: true - /cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} + cliui@8.0.1: dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - dev: true - /clone-response@1.0.3: - resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} + clone-response@1.0.3: dependencies: mimic-response: 1.0.1 - dev: false - /cluster-key-slot@1.1.2: - resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} - engines: {node: '>=0.10.0'} - dev: false + cluster-key-slot@1.1.2: {} - /co@4.6.0: - resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} - engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - dev: true + co@4.6.0: {} - /collect-v8-coverage@1.0.2: - resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} - dev: true + collect-v8-coverage@1.0.2: {} - /color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + color-convert@1.9.3: dependencies: color-name: 1.1.3 - /color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} + color-convert@2.0.1: dependencies: color-name: 1.1.4 - /color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + color-name@1.1.3: {} - /color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-name@1.1.4: {} - /commander@11.1.0: - resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} - engines: {node: '>=16'} - dev: false + commander@11.1.0: {} - /commander@12.0.0: - resolution: {integrity: sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==} - engines: {node: '>=18'} - dev: false + commander@12.0.0: {} - /common-tags@1.8.2: - resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} - engines: {node: '>=4.0.0'} - dev: true + common-tags@1.8.2: {} - /commondir@1.0.1: - resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} - dev: true + commondir@1.0.1: {} - /compare-func@2.0.0: - resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + compare-func@2.0.0: dependencies: array-ify: 1.0.0 dot-prop: 5.3.0 - dev: true - /concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + concat-map@0.0.1: {} - /config-chain@1.1.13: - resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + config-chain@1.1.13: dependencies: ini: 1.3.8 proto-list: 1.2.4 - dev: true - /conventional-changelog-angular@7.0.0: - resolution: {integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==} - engines: {node: '>=16'} + conventional-changelog-angular@7.0.0: dependencies: compare-func: 2.0.0 - dev: true - /conventional-changelog-conventionalcommits@7.0.2: - resolution: {integrity: sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==} - engines: {node: '>=16'} + conventional-changelog-conventionalcommits@7.0.2: dependencies: compare-func: 2.0.0 - dev: true - /conventional-changelog-writer@7.0.1: - resolution: {integrity: sha512-Uo+R9neH3r/foIvQ0MKcsXkX642hdm9odUp7TqgFS7BsalTcjzRlIfWZrZR1gbxOozKucaKt5KAbjW8J8xRSmA==} - engines: {node: '>=16'} - hasBin: true + conventional-changelog-writer@7.0.1: dependencies: conventional-commits-filter: 4.0.0 handlebars: 4.7.8 json-stringify-safe: 5.0.1 meow: 12.1.1 - semver: 7.5.4 + semver: 7.6.2 split2: 4.2.0 - dev: true - /conventional-commits-detector@1.0.3: - resolution: {integrity: sha512-VlBCTEg34Bbvyh7MPYtmgoYPsP69Z1BusmthbiUbzTiwfhLZWRDEWsJHqWyiekSC9vFCHGT/jKOzs8r21MUZ5g==} - engines: {node: '>=6.9.0'} - hasBin: true + conventional-commits-detector@1.0.3: dependencies: arrify: 1.0.1 git-raw-commits: 2.0.11 meow: 7.1.1 through2-concurrent: 2.0.0 - dev: false - /conventional-commits-filter@4.0.0: - resolution: {integrity: sha512-rnpnibcSOdFcdclpFwWa+pPlZJhXE7l+XK04zxhbWrhgpR96h33QLz8hITTXbcYICxVr3HZFtbtUAQ+4LdBo9A==} - engines: {node: '>=16'} - dev: true + conventional-commits-filter@4.0.0: {} - /conventional-commits-parser@5.0.0: - resolution: {integrity: sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==} - engines: {node: '>=16'} - hasBin: true + conventional-commits-parser@5.0.0: dependencies: JSONStream: 1.3.5 is-text-path: 2.0.0 meow: 12.1.1 split2: 4.2.0 - dev: true - /convert-source-map@1.9.0: - resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - dev: true + convert-source-map@1.9.0: {} - /convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - dev: true + convert-source-map@2.0.0: {} - /core-js-pure@3.36.0: - resolution: {integrity: sha512-cN28qmhRNgbMZZMc/RFu5w8pK9VJzpb2rJVR/lHuZJKwmXnoWOpXmMkxqBB514igkp1Hu8WGROsiOAzUcKdHOQ==} - requiresBuild: true - dev: false + core-js-pure@3.37.0: {} - /core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + core-util-is@1.0.3: {} - /cosmiconfig@8.3.6(typescript@5.3.3): - resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} - engines: {node: '>=14'} - peerDependencies: - typescript: '>=4.9.5' - peerDependenciesMeta: - typescript: - optional: true + cosmiconfig@8.3.6(typescript@5.4.5): dependencies: import-fresh: 3.3.0 js-yaml: 4.1.0 parse-json: 5.2.0 path-type: 4.0.0 - typescript: 5.3.3 - dev: true + optionalDependencies: + typescript: 5.4.5 - /create-jest@29.7.0(@types/node@18.19.21)(ts-node@10.9.2): - resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true + create-jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@18.19.21)(ts-node@10.9.2) + jest-config: 29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -5153,403 +9571,276 @@ packages: - babel-plugin-macros - supports-color - ts-node - dev: true - /create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - dev: true + create-require@1.1.1: {} - /cron-parser@4.9.0: - resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} - engines: {node: '>=12.0.0'} + cron-parser@4.9.0: dependencies: luxon: 3.4.4 - dev: false - /cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} + cross-spawn@7.0.3: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - /crypto-random-string@4.0.0: - resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} - engines: {node: '>=12'} + crypto-random-string@4.0.0: dependencies: type-fest: 1.4.0 - dev: true - /css-select@5.1.0: - resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + css-select@5.1.0: dependencies: boolbase: 1.0.0 css-what: 6.1.0 domhandler: 5.0.3 domutils: 3.1.0 nth-check: 2.1.1 - dev: false - /css-what@6.1.0: - resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} - engines: {node: '>= 6'} - dev: false + css-what@6.1.0: {} - /dargs@7.0.0: - resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} - engines: {node: '>=8'} - dev: false + dargs@7.0.0: {} - /debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + data-view-buffer@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-offset@1.0.0: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + debug@3.2.7: dependencies: ms: 2.1.3 - dev: true - /debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + debug@4.3.4: dependencies: ms: 2.1.2 - /decamelize-keys@1.1.1: - resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} - engines: {node: '>=0.10.0'} + decamelize-keys@1.1.1: dependencies: decamelize: 1.2.0 map-obj: 1.0.1 - dev: false - /decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} + decamelize@1.2.0: {} - /decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} + decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 - dev: false - /dedent@1.5.1: - resolution: {integrity: sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==} - peerDependencies: - babel-plugin-macros: ^3.1.0 - peerDependenciesMeta: - babel-plugin-macros: - optional: true - dev: true + dedent@1.5.3: {} - /deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} + deep-equal@2.2.3: + dependencies: + array-buffer-byte-length: 1.0.1 + call-bind: 1.0.7 + es-get-iterator: 1.1.3 + get-intrinsic: 1.2.4 + is-arguments: 1.1.1 + is-array-buffer: 3.0.4 + is-date-object: 1.0.5 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.3 + isarray: 2.0.5 + object-is: 1.1.6 + object-keys: 1.1.1 + object.assign: 4.1.5 + regexp.prototype.flags: 1.5.2 + side-channel: 1.0.6 + which-boxed-primitive: 1.0.2 + which-collection: 1.0.2 + which-typed-array: 1.1.15 - /deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - dev: true + deep-extend@0.6.0: {} - /deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} + deep-is@0.1.4: {} - /default-require-extensions@3.0.1: - resolution: {integrity: sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==} - engines: {node: '>=8'} + deepmerge@4.3.1: {} + + default-require-extensions@3.0.1: dependencies: strip-bom: 4.0.0 - dev: true - /defer-to-connect@2.0.1: - resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} - engines: {node: '>=10'} - dev: false + defer-to-connect@2.0.1: {} - /define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} + define-data-property@1.1.4: dependencies: es-define-property: 1.0.0 es-errors: 1.3.0 gopd: 1.0.1 - /define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} + define-properties@1.2.1: dependencies: define-data-property: 1.1.4 has-property-descriptors: 1.0.2 object-keys: 1.1.1 - /deprecation@2.3.1: - resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} + deprecation@2.3.1: {} - /dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - dev: false + dequal@2.0.3: {} - /detect-indent@6.1.0: - resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} - engines: {node: '>=8'} - dev: false + detect-indent@6.1.0: {} - /detect-libc@2.0.2: - resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} - engines: {node: '>=8'} - dev: false + detect-libc@2.0.3: + optional: true - /detect-newline@3.1.0: - resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} - engines: {node: '>=8'} - dev: true + detect-newline@3.1.0: {} - /detect-node@2.1.0: - resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} - dev: false + detect-node@2.1.0: {} - /diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: true + diff-sequences@29.6.3: {} - /diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} - engines: {node: '>=0.3.1'} - dev: true + diff@4.0.2: {} - /diff@5.2.0: - resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} - engines: {node: '>=0.3.1'} + diff@5.2.0: {} - /dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 - dev: true - /doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} + doctrine@2.1.0: dependencies: esutils: 2.0.3 - dev: true - /doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} + doctrine@3.0.0: dependencies: esutils: 2.0.3 - dev: true - /dom-serializer@2.0.0: - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 domhandler: 5.0.3 entities: 4.5.0 - dev: false - /domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - dev: false + domelementtype@2.3.0: {} - /domhandler@5.0.3: - resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} - engines: {node: '>= 4'} + domhandler@5.0.3: dependencies: domelementtype: 2.3.0 - dev: false - /domutils@3.1.0: - resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} + domutils@3.1.0: dependencies: dom-serializer: 2.0.0 domelementtype: 2.3.0 domhandler: 5.0.3 - dev: false - /dot-prop@5.3.0: - resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} - engines: {node: '>=8'} + dot-prop@5.3.0: dependencies: is-obj: 2.0.0 - dev: true - /dotenv@16.4.5: - resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} - engines: {node: '>=12'} - dev: false + dotenv@16.4.5: {} - /dtrace-provider@0.8.8: - resolution: {integrity: sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==} - engines: {node: '>=0.10'} - requiresBuild: true + dtrace-provider@0.8.8: dependencies: - nan: 2.18.0 - dev: false + nan: 2.19.0 optional: true - /duplexer2@0.1.4: - resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + duplexer2@0.1.4: dependencies: readable-stream: 2.3.8 - dev: true - /eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + eastasianwidth@0.2.0: {} - /ecdsa-sig-formatter@1.0.11: - resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer: 5.2.1 - dev: false - /editorconfig@2.0.0: - resolution: {integrity: sha512-s1NQ63WQ7RNXH6Efb2cwuyRlfpbtdZubvfNe4vCuoyGPewNPY7vah8JUSOFBiJ+jr99Qh8t0xKv0oITc1dclgw==} - engines: {node: '>=16'} - hasBin: true + editorconfig@2.0.0: dependencies: '@one-ini/wasm': 0.1.1 commander: 11.1.0 minimatch: 9.0.2 - semver: 7.5.4 - dev: false + semver: 7.6.2 - /electron-to-chromium@1.4.681: - resolution: {integrity: sha512-1PpuqJUFWoXZ1E54m8bsLPVYwIVCRzvaL+n5cjigGga4z854abDnFRc+cTa2th4S79kyGqya/1xoR7h+Y5G5lg==} - dev: true + electron-to-chromium@1.4.756: {} - /email-addresses@5.0.0: - resolution: {integrity: sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==} - dev: false + email-addresses@5.0.0: {} - /emittery@0.13.1: - resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} - engines: {node: '>=12'} - dev: true + emittery@0.13.1: {} - /emoji-regex@10.3.0: - resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==} - dev: false + emoji-regex@10.3.0: {} - /emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emoji-regex@8.0.0: {} - /emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + emoji-regex@9.2.2: {} - /emojibase-data@15.3.0(emojibase@15.3.0): - resolution: {integrity: sha512-cYsGq57W8wOd1CqQCjmtP1DpmlTVvJY+Um5UobWUQuTnqb8cO2cuqrxJxSIqxLcYZ3rtialT5kivoWigszdslg==} - peerDependencies: - emojibase: '*' + emojibase-data@15.3.0(emojibase@15.3.0): dependencies: emojibase: 15.3.0 - dev: true - /emojibase-regex@15.3.0: - resolution: {integrity: sha512-EBz/292VBF9naBPBsGzkZUccgIv1xJibTXIINl8SezgVRnTCpKJx7MgZcR+UAd2RwjGkRJJZ/lhP7riOFZLicA==} - dev: false + emojibase-regex@15.3.0: {} - /emojibase@15.3.0: - resolution: {integrity: sha512-lFdQb14hoznwDh1xDoOFzkPdeeexmbuJdhUUjDaJZf/+jK+6Z3HkI5hWOemWfEdaMxtTujc1vNRx9DKtdtiOXA==} + emojibase@15.3.0: {} - /emojilib@2.4.0: - resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} - dev: true + emojilib@2.4.0: {} - /encoding@0.1.13: - resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} - requiresBuild: true + encoding@0.1.13: dependencies: iconv-lite: 0.6.3 - dev: false optional: true - /end-of-stream@1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + end-of-stream@1.4.4: dependencies: once: 1.4.0 - dev: false - /enhanced-resolve@5.15.0: - resolution: {integrity: sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==} - engines: {node: '>=10.13.0'} + enhanced-resolve@5.16.0: dependencies: graceful-fs: 4.2.11 tapable: 2.2.1 - dev: true - - /entities@3.0.1: - resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==} - engines: {node: '>=0.12'} - dev: false - /entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} + entities@4.5.0: {} - /env-ci@10.0.0: - resolution: {integrity: sha512-U4xcd/utDYFgMh0yWj07R1H6L5fwhVbmxBCpnL0DbVSDZVnsC82HONw0wxtxNkIAcua3KtbomQvIk5xFZGAQJw==} - engines: {node: ^18.17 || >=20.6.1} + env-ci@10.0.0: dependencies: execa: 8.0.1 java-properties: 1.0.2 - dev: true - /env-paths@2.2.1: - resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} - engines: {node: '>=6'} - requiresBuild: true - dev: false + env-paths@2.2.1: optional: true - /err-code@2.0.3: - resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} - requiresBuild: true - dev: false + err-code@2.0.3: optional: true - /error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 - /es-abstract@1.22.4: - resolution: {integrity: sha512-vZYJlk2u6qHYxBOTjAeg7qUxHdNfih64Uu2J8QqWgXZ2cri0ZpJAkzDUK/q593+mvKwlxyaxr6F1Q+3LKoQRgg==} - engines: {node: '>= 0.4'} + es-abstract@1.23.3: dependencies: array-buffer-byte-length: 1.0.1 arraybuffer.prototype.slice: 1.0.3 available-typed-arrays: 1.0.7 call-bind: 1.0.7 + data-view-buffer: 1.0.1 + data-view-byte-length: 1.0.1 + data-view-byte-offset: 1.0.0 es-define-property: 1.0.0 es-errors: 1.3.0 + es-object-atoms: 1.0.0 es-set-tostringtag: 2.0.3 es-to-primitive: 1.2.1 function.prototype.name: 1.1.6 get-intrinsic: 1.2.4 get-symbol-description: 1.0.2 - globalthis: 1.0.3 + globalthis: 1.0.4 gopd: 1.0.1 has-property-descriptors: 1.0.2 has-proto: 1.0.3 has-symbols: 1.0.3 - hasown: 2.0.1 + hasown: 2.0.2 internal-slot: 1.0.7 is-array-buffer: 3.0.4 is-callable: 1.2.7 + is-data-view: 1.0.1 is-negative-zero: 2.0.3 is-regex: 1.1.4 is-shared-array-buffer: 1.0.3 @@ -5560,138 +9851,98 @@ packages: object-keys: 1.1.1 object.assign: 4.1.5 regexp.prototype.flags: 1.5.2 - safe-array-concat: 1.1.0 + safe-array-concat: 1.1.2 safe-regex-test: 1.0.3 - string.prototype.trim: 1.2.8 - string.prototype.trimend: 1.0.7 - string.prototype.trimstart: 1.0.7 + string.prototype.trim: 1.2.9 + string.prototype.trimend: 1.0.8 + string.prototype.trimstart: 1.0.8 typed-array-buffer: 1.0.2 typed-array-byte-length: 1.0.1 typed-array-byte-offset: 1.0.2 - typed-array-length: 1.0.5 + typed-array-length: 1.0.6 unbox-primitive: 1.0.2 - which-typed-array: 1.1.14 - dev: true + which-typed-array: 1.1.15 - /es-array-method-boxes-properly@1.0.0: - resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==} - dev: true + es-define-property@1.0.0: + dependencies: + get-intrinsic: 1.2.4 - /es-define-property@1.0.0: - resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} - engines: {node: '>= 0.4'} + es-errors@1.3.0: {} + + es-get-iterator@1.1.3: dependencies: + call-bind: 1.0.7 get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + is-arguments: 1.1.1 + is-map: 2.0.3 + is-set: 2.0.3 + is-string: 1.0.7 + isarray: 2.0.5 + stop-iteration-iterator: 1.0.0 - /es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} + es-object-atoms@1.0.0: + dependencies: + es-errors: 1.3.0 - /es-set-tostringtag@2.0.3: - resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} - engines: {node: '>= 0.4'} + es-set-tostringtag@2.0.3: dependencies: get-intrinsic: 1.2.4 has-tostringtag: 1.0.2 - hasown: 2.0.1 - dev: true + hasown: 2.0.2 - /es-shim-unscopables@1.0.2: - resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} + es-shim-unscopables@1.0.2: dependencies: - hasown: 2.0.1 - dev: true + hasown: 2.0.2 - /es-to-primitive@1.2.1: - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} - engines: {node: '>= 0.4'} + es-to-primitive@1.2.1: dependencies: is-callable: 1.2.7 is-date-object: 1.0.5 is-symbol: 1.0.4 - dev: true - - /es6-error@4.1.1: - resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} - /escalade@3.1.2: - resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} - engines: {node: '>=6'} - dev: true + es6-error@4.1.1: {} - /escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} + escalade@3.1.2: {} - /escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} - dev: true + escape-string-regexp@1.0.5: {} - /escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} + escape-string-regexp@2.0.0: {} - /escape-string-regexp@5.0.0: - resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} - engines: {node: '>=12'} - dev: true + escape-string-regexp@4.0.0: {} - /eslint-config-prettier@9.1.0(eslint@8.56.0): - resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' - dependencies: - eslint: 8.56.0 - dev: true + escape-string-regexp@5.0.0: {} - /eslint-formatter-gha@1.4.3: - resolution: {integrity: sha512-lnuccy7JUn/KZfRgUw9Sb81x4esxJMpaGXNniv1UKT6llYhRpCuo64SgkYEkkPGSfkRwujuP8Xju5M8oqXt4HQ==} + eslint-formatter-gha@1.5.0: dependencies: - '@actions/core': 1.10.1 eslint-formatter-json: 8.40.0 eslint-formatter-stylish: 8.40.0 - dev: true - /eslint-formatter-json@8.40.0: - resolution: {integrity: sha512-0bXo4At1EoEU23gFfN7wcDeqRXDHLJnvDOuQKD3Q6FkBlk7L2oVNPYg/sciIWdYrUnCBcKuMit3IWXkdSfzChg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true + eslint-formatter-json@8.40.0: {} - /eslint-formatter-stylish@8.40.0: - resolution: {integrity: sha512-blbD5ZSQnjNEUaG38VCO4WG9nfDQWE8/IOmt8DFRHXUIfZikaIXmsQTdWNFk0/e0j7RgIVRza86MpsJ+aHgFLg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-formatter-stylish@8.40.0: dependencies: chalk: 4.1.2 strip-ansi: 6.0.1 text-table: 0.2.0 - dev: true - /eslint-import-resolver-node@0.3.9: - resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + eslint-import-resolver-node@0.3.9: dependencies: debug: 3.2.7 is-core-module: 2.13.1 resolve: 1.22.8 transitivePeerDependencies: - supports-color - dev: true - /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0): - resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - eslint: '*' - eslint-plugin-import: '*' + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0): dependencies: debug: 4.3.4 - enhanced-resolve: 5.15.0 - eslint: 8.56.0 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) + enhanced-resolve: 5.16.0 + eslint: 8.57.0 + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 - get-tsconfig: 4.7.2 + get-tsconfig: 4.7.4 is-core-module: 2.13.1 is-glob: 4.0.3 transitivePeerDependencies: @@ -5699,152 +9950,90 @@ packages: - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0): - resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true + eslint-module-utils@2.8.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): dependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3) debug: 3.2.7 - eslint: 8.56.0 + optionalDependencies: + '@typescript-eslint/parser': 7.10.0(eslint@8.57.0)(typescript@5.4.5) + eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0) transitivePeerDependencies: - supports-color - dev: true - /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0): - resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true + eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3) - array-includes: 3.1.7 - array.prototype.findlastindex: 1.2.4 + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 array.prototype.flatmap: 1.3.2 debug: 3.2.7 doctrine: 2.1.0 - eslint: 8.56.0 + eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) - hasown: 2.0.1 + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + hasown: 2.0.2 is-core-module: 2.13.1 is-glob: 4.0.3 minimatch: 3.1.2 - object.fromentries: 2.0.7 - object.groupby: 1.0.2 - object.values: 1.1.7 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.0 semver: 6.3.1 tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 7.10.0(eslint@8.57.0)(typescript@5.4.5) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - dev: true - /eslint-plugin-jest-formatting@3.1.0(eslint@8.56.0): - resolution: {integrity: sha512-XyysraZ1JSgGbLSDxjj5HzKKh0glgWf+7CkqxbTqb7zEhW7X2WHo5SBQ8cGhnszKN+2Lj3/oevBlHNbHezoc/A==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: '>=0.8.0' + eslint-plugin-jest-formatting@3.1.0(eslint@8.57.0): dependencies: - eslint: 8.56.0 - dev: true + eslint: 8.57.0 - /eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.56.0)(jest@29.7.0)(typescript@5.3.3): - resolution: {integrity: sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==} - 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 + eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.10.0(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5)))(typescript@5.4.5): dependencies: - '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3) - '@typescript-eslint/utils': 5.62.0(eslint@8.56.0)(typescript@5.3.3) - eslint: 8.56.0 - jest: 29.7.0(@types/node@18.19.21)(ts-node@10.9.2) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.4.5) + eslint: 8.57.0 + optionalDependencies: + '@typescript-eslint/eslint-plugin': 7.10.0(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + jest: 29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5)) transitivePeerDependencies: - supports-color - typescript - dev: true - /eslint-plugin-promise@6.1.1(eslint@8.56.0): - resolution: {integrity: sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 + eslint-plugin-promise@6.1.1(eslint@8.57.0): dependencies: - eslint: 8.56.0 - dev: true + eslint: 8.57.0 - /eslint-plugin-typescript-enum@2.1.0(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-n6RO89KJ2V2nHVAdIq1q3IBeYZSNZjBreqXOzpjmsBtw+NNhSTTSQXqwO00VYOce9Gy8cr2cDEYpj0Km+Ij90Q==} + eslint-plugin-typescript-enum@2.1.0(eslint@8.57.0)(typescript@5.4.5): dependencies: - '@typescript-eslint/experimental-utils': 5.62.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/experimental-utils': 5.62.0(eslint@8.57.0)(typescript@5.4.5) transitivePeerDependencies: - eslint - supports-color - typescript - dev: true - /eslint-scope@5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} + eslint-scope@5.1.1: dependencies: esrecurse: 4.3.0 estraverse: 4.3.0 - dev: true - /eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-scope@7.2.2: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 - dev: true - /eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-visitor-keys@3.4.3: {} - /eslint@8.56.0: - resolution: {integrity: sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true + eslint@8.57.0: dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@eslint-community/regexpp': 4.10.0 '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.56.0 + '@eslint/js': 8.57.0 '@humanwhocodes/config-array': 0.11.14 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 @@ -5876,63 +10065,37 @@ packages: lodash.merge: 4.6.2 minimatch: 3.1.2 natural-compare: 1.4.0 - optionator: 0.9.3 + optionator: 0.9.4 strip-ansi: 6.0.1 text-table: 0.2.0 transitivePeerDependencies: - supports-color - dev: true - /espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + espree@9.6.1: dependencies: acorn: 8.11.3 acorn-jsx: 5.3.2(acorn@8.11.3) eslint-visitor-keys: 3.4.3 - dev: true - /esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true + esprima@4.0.1: {} - /esquery@1.5.0: - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} - engines: {node: '>=0.10'} + esquery@1.5.0: dependencies: estraverse: 5.3.0 - dev: true - /esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} + esrecurse@4.3.0: dependencies: estraverse: 5.3.0 - dev: true - /estraverse@4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} - dev: true + estraverse@4.3.0: {} - /estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - dev: true + estraverse@5.3.0: {} - /esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - dev: true + esutils@2.0.3: {} - /eventemitter3@4.0.7: - resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - dev: false + eventemitter3@4.0.7: {} - /execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} + execa@5.1.1: dependencies: cross-spawn: 7.0.3 get-stream: 6.0.1 @@ -5943,11 +10106,8 @@ packages: onetime: 5.1.2 signal-exit: 3.0.7 strip-final-newline: 2.0.0 - dev: true - /execa@8.0.1: - resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} - engines: {node: '>=16.17'} + execa@8.0.1: dependencies: cross-spawn: 7.0.3 get-stream: 8.0.1 @@ -5958,54 +10118,34 @@ packages: onetime: 6.0.0 signal-exit: 4.1.0 strip-final-newline: 3.0.0 - dev: true - /exit@0.1.2: - resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} - engines: {node: '>= 0.8.0'} - dev: true + exit@0.1.2: {} - /expand-template@2.0.3: - resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} - engines: {node: '>=6'} - dev: false + expand-template@2.0.3: + optional: true - /expect-more-jest@5.5.0: - resolution: {integrity: sha512-l3SwYCvT02r97uFlJnFQQiFGFEAdt6zHBiFDUiOuAfPxM1/kVGpzNXw9UM66WieNsK/e/G+UzuW39GGxqGjG8A==} + expect-more-jest@5.5.0: dependencies: '@jest/expect-utils': 29.4.1 expect-more: 1.3.0 jest-matcher-utils: 29.4.1 - dev: true - /expect-more@1.3.0: - resolution: {integrity: sha512-HnXT5nJb9V3DMnr5RgA1TiKbu5kRaJ0GD1JkuhZvnr1Qe3HJq+ESnrcl/jmVUZ8Ycnl3Sp0OTYUhmO36d2+zow==} - dev: true + expect-more@1.3.0: {} - /expect@29.7.0: - resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + expect@29.7.0: 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 - dev: true - /exponential-backoff@3.1.1: - resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==} - requiresBuild: true - dev: false + exponential-backoff@3.1.1: optional: true - /extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + extend@3.0.2: {} - /extract-zip@2.0.1: - resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} - engines: {node: '>= 10.17.0'} - hasBin: true + extract-zip@2.0.1: dependencies: debug: 4.3.4 get-stream: 5.2.0 @@ -6014,14 +10154,10 @@ packages: '@types/yauzl': 2.10.3 transitivePeerDependencies: - supports-color - dev: false - /fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-deep-equal@3.1.3: {} - /fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} - engines: {node: '>=8.6.0'} + fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 '@nodelib/fs.walk': 1.2.8 @@ -6029,393 +10165,253 @@ packages: merge2: 1.4.1 micromatch: 4.0.5 - /fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - dev: true + fast-json-stable-stringify@2.1.0: {} - /fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - dev: true + fast-levenshtein@2.0.6: {} - /fast-xml-parser@4.2.5: - resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==} - hasBin: true + fast-xml-parser@4.2.5: dependencies: strnum: 1.0.5 - dev: false - /fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + fastq@1.17.1: dependencies: reusify: 1.0.4 - /fb-watchman@2.0.2: - resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + fb-watchman@2.0.2: dependencies: bser: 2.1.1 - dev: true - /fd-slicer@1.1.0: - resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + fd-slicer@1.1.0: dependencies: pend: 1.2.0 - dev: false - /figures@2.0.0: - resolution: {integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==} - engines: {node: '>=4'} + figures@2.0.0: dependencies: escape-string-regexp: 1.0.5 - dev: true - /figures@6.0.1: - resolution: {integrity: sha512-0oY/olScYD4IhQ8u//gCPA4F3mlTn2dacYmiDm/mbDQvpmLjV4uH+zhsQ5IyXRyvqkvtUkXkNdGvg5OFJTCsuQ==} - engines: {node: '>=18'} + figures@6.1.0: dependencies: is-unicode-supported: 2.0.0 - dev: true - /file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 - dev: true - /file-uri-to-path@1.0.0: - resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - dev: false + file-uri-to-path@1.0.0: + optional: true - /fill-range@7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} + fill-range@7.0.1: dependencies: to-regex-range: 5.0.1 - /find-cache-dir@3.3.2: - resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} - engines: {node: '>=8'} + find-cache-dir@3.3.2: dependencies: commondir: 1.0.1 make-dir: 3.1.0 pkg-dir: 4.2.0 - dev: true - /find-packages@10.0.4: - resolution: {integrity: sha512-JmO9lEBUEYOiRw/bdbdgFWpGFgBZBGLcK/5GjQKo3ZN+zR6jmQOh9gWyZoqxlQmnldZ9WBWhna0QYyuq6BxvRg==} - engines: {node: '>=14.6'} + find-packages@10.0.4: dependencies: '@pnpm/read-project-manifest': 4.1.1 '@pnpm/types': 8.9.0 '@pnpm/util.lex-comparator': 1.0.0 fast-glob: 3.3.2 p-filter: 2.1.0 - dev: false - /find-up-simple@1.0.0: - resolution: {integrity: sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==} - engines: {node: '>=18'} - dev: true + find-up-simple@1.0.0: {} - /find-up@2.1.0: - resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} - engines: {node: '>=4'} + find-up@2.1.0: dependencies: locate-path: 2.0.0 - dev: true - /find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} + find-up@4.1.0: dependencies: locate-path: 5.0.0 path-exists: 4.0.0 - /find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} + find-up@5.0.0: dependencies: locate-path: 6.0.0 path-exists: 4.0.0 - /find-versions@5.1.0: - resolution: {integrity: sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==} - engines: {node: '>=12'} + find-versions@5.1.0: dependencies: semver-regex: 4.0.5 - dev: true - /flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} + flat-cache@3.2.0: dependencies: flatted: 3.3.1 keyv: 4.5.4 rimraf: 3.0.2 - dev: true - /flatted@3.3.1: - resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} - dev: true + flatted@3.3.1: {} - /for-each@0.3.3: - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + for-each@0.3.3: dependencies: is-callable: 1.2.7 - /foreground-child@2.0.0: - resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==} - engines: {node: '>=8.0.0'} + foreground-child@2.0.0: dependencies: cross-spawn: 7.0.3 signal-exit: 3.0.7 - dev: true - /foreground-child@3.1.1: - resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} - engines: {node: '>=14'} + foreground-child@3.1.1: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 - /from2@2.3.0: - resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==} + from2@2.3.0: dependencies: inherits: 2.0.4 readable-stream: 2.3.8 - dev: true - /fromentries@1.3.2: - resolution: {integrity: sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==} - dev: true + fromentries@1.3.2: {} - /fs-constants@1.0.0: - resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - dev: false + fs-constants@1.0.0: + optional: true - /fs-extra@11.2.0: - resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} - engines: {node: '>=14.14'} + fs-extra@11.2.0: dependencies: graceful-fs: 4.2.11 jsonfile: 6.1.0 universalify: 2.0.1 - /fs-minipass@2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} + fs-minipass@2.1.0: dependencies: minipass: 3.3.6 - /fs-minipass@3.0.3: - resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + fs-minipass@3.0.3: dependencies: - minipass: 7.0.4 - dev: false + minipass: 7.1.0 - /fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - dev: true + fs.realpath@1.0.0: {} - /fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - dev: true + fsevents@2.3.3: optional: true - /function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + function-bind@1.1.2: {} - /function.prototype.name@1.1.6: - resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} - engines: {node: '>= 0.4'} + function.prototype.name@1.1.6: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.4 + es-abstract: 1.23.3 functions-have-names: 1.2.3 - dev: true - /functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - dev: true + functions-have-names@1.2.3: {} - /gaxios@6.3.0: - resolution: {integrity: sha512-p+ggrQw3fBwH2F5N/PAI4k/G/y1art5OxKpb2J2chwNNHM4hHuAOtivjPuirMF4KNKwTTUal/lPfL2+7h2mEcg==} - engines: {node: '>=14'} + gaxios@6.5.0(encoding@0.1.13): dependencies: extend: 3.0.2 https-proxy-agent: 7.0.4 is-stream: 2.0.1 - node-fetch: 2.7.0 + node-fetch: 2.7.0(encoding@0.1.13) + uuid: 9.0.1 transitivePeerDependencies: - encoding - supports-color - dev: false - /gcp-metadata@6.1.0: - resolution: {integrity: sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==} - engines: {node: '>=14'} + gcp-metadata@6.1.0(encoding@0.1.13): dependencies: - gaxios: 6.3.0 + gaxios: 6.5.0(encoding@0.1.13) json-bigint: 1.0.0 transitivePeerDependencies: - encoding - supports-color - dev: false - - /generic-pool@3.9.0: - resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} - engines: {node: '>= 4'} - dev: false - /gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - dev: true + generic-pool@3.9.0: {} - /get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - dev: true + gensync@1.0.0-beta.2: {} - /get-intrinsic@1.2.4: - resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} - engines: {node: '>= 0.4'} + get-caller-file@2.0.5: {} + + get-intrinsic@1.2.4: dependencies: es-errors: 1.3.0 function-bind: 1.1.2 has-proto: 1.0.3 has-symbols: 1.0.3 - hasown: 2.0.1 + hasown: 2.0.2 - /get-package-type@0.1.0: - resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} - engines: {node: '>=8.0.0'} - dev: true + get-package-type@0.1.0: {} - /get-stream@5.2.0: - resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} - engines: {node: '>=8'} + get-stream@5.2.0: dependencies: pump: 3.0.0 - dev: false - /get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - dev: true + get-stream@6.0.1: {} - /get-stream@7.0.1: - resolution: {integrity: sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==} - engines: {node: '>=16'} - dev: true + get-stream@7.0.1: {} - /get-stream@8.0.1: - resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} - engines: {node: '>=16'} - dev: true + get-stream@8.0.1: {} - /get-symbol-description@1.0.2: - resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} - engines: {node: '>= 0.4'} + get-symbol-description@1.0.2: dependencies: call-bind: 1.0.7 es-errors: 1.3.0 get-intrinsic: 1.2.4 - dev: true - /get-tsconfig@4.7.2: - resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} + get-tsconfig@4.7.4: dependencies: resolve-pkg-maps: 1.0.0 - dev: true - /git-log-parser@1.2.0: - resolution: {integrity: sha512-rnCVNfkTL8tdNryFuaY0fYiBWEBcgF748O6ZI61rslBvr2o7U65c2/6npCRqH40vuAhtgtDiqLTJjBVdrejCzA==} + git-log-parser@1.2.0: dependencies: argv-formatter: 1.0.0 spawn-error-forwarder: 1.0.0 split2: 1.0.0 stream-combiner2: 1.1.1 through2: 2.0.5 - traverse: 0.6.8 - dev: true + traverse: 0.6.9 - /git-raw-commits@2.0.11: - resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==} - engines: {node: '>=10'} - hasBin: true + git-raw-commits@2.0.11: dependencies: dargs: 7.0.0 lodash: 4.17.21 meow: 8.1.2 split2: 3.2.2 through2: 4.0.2 - dev: false - /git-up@7.0.0: - resolution: {integrity: sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==} + git-up@7.0.0: dependencies: is-ssh: 1.4.0 parse-url: 8.1.0 - dev: false - /git-url-parse@14.0.0: - resolution: {integrity: sha512-NnLweV+2A4nCvn4U/m2AoYu0pPKlsmhK9cknG7IMwsjFY1S2jxM+mAhsDxyxfCIGfGaD+dozsyX4b6vkYc83yQ==} + git-url-parse@14.0.0: dependencies: git-up: 7.0.0 - dev: false - /github-from-package@0.0.0: - resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} - dev: false + github-from-package@0.0.0: + optional: true - /github-url-from-git@1.5.0: - resolution: {integrity: sha512-WWOec4aRI7YAykQ9+BHmzjyNlkfJFG8QLXnDTsLz/kZefq7qkzdfo4p6fkYYMIq1aj+gZcQs/1HQhQh3DPPxlQ==} - dev: false + github-url-from-git@1.5.0: {} - /glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 - /glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 - dev: true - /glob@10.3.10: - resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true + glob@10.3.15: dependencies: foreground-child: 3.1.1 jackspeak: 2.3.6 - minimatch: 9.0.3 - minipass: 7.0.4 - path-scurry: 1.10.1 + minimatch: 9.0.4 + minipass: 7.1.0 + path-scurry: 1.11.1 - /glob@6.0.4: - resolution: {integrity: sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==} - requiresBuild: true + glob@6.0.4: dependencies: inflight: 1.0.6 inherits: 2.0.4 minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 - dev: false optional: true - /glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + glob@7.2.3: dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -6423,41 +10419,28 @@ packages: minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 - dev: true - /global-agent@3.0.0: - resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} - engines: {node: '>=10.0'} + global-agent@3.0.0: dependencies: boolean: 3.2.0 es6-error: 4.1.1 matcher: 3.0.0 roarr: 2.15.4 - semver: 7.5.4 + semver: 7.6.2 serialize-error: 7.0.1 - dev: false - /globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - dev: true + globals@11.12.0: {} - /globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} + globals@13.24.0: dependencies: type-fest: 0.20.2 - dev: true - /globalthis@1.0.3: - resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} - engines: {node: '>= 0.4'} + globalthis@1.0.4: dependencies: define-properties: 1.2.1 + gopd: 1.0.1 - /globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} + globby@11.1.0: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 @@ -6465,23 +10448,8 @@ packages: ignore: 5.3.1 merge2: 1.4.1 slash: 3.0.0 - dev: true - - /globby@14.0.0: - resolution: {integrity: sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==} - engines: {node: '>=18'} - dependencies: - '@sindresorhus/merge-streams': 1.0.0 - fast-glob: 3.3.2 - ignore: 5.3.1 - path-type: 5.0.0 - slash: 5.1.0 - unicorn-magic: 0.1.0 - dev: true - /globby@14.0.1: - resolution: {integrity: sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==} - engines: {node: '>=18'} + globby@14.0.1: dependencies: '@sindresorhus/merge-streams': 2.3.0 fast-glob: 3.3.2 @@ -6489,41 +10457,31 @@ packages: path-type: 5.0.0 slash: 5.1.0 unicorn-magic: 0.1.0 - dev: true - /good-enough-parser@1.1.23: - resolution: {integrity: sha512-QUcQZutczESpdo2w9BMG6VpLFoq9ix7ER5HLM1mAdZdri2F3eISkCb8ep84W6YOo0grYWJdyT/8JkYqGjQfSSQ==} - engines: {node: '>=18.12.0', yarn: ^1.17.0} + good-enough-parser@1.1.23: dependencies: '@thi.ng/zipper': 1.0.3 '@types/moo': 0.5.5 klona: 2.0.6 moo: 0.5.2 - dev: false - /google-auth-library@9.6.3: - resolution: {integrity: sha512-4CacM29MLC2eT9Cey5GDVK4Q8t+MMp8+OEdOaqD9MG6b0dOyLORaaeJMPQ7EESVgm/+z5EKYyFLxgzBJlJgyHQ==} - engines: {node: '>=14'} + google-auth-library@9.10.0(encoding@0.1.13): dependencies: base64-js: 1.5.1 ecdsa-sig-formatter: 1.0.11 - gaxios: 6.3.0 - gcp-metadata: 6.1.0 - gtoken: 7.1.0 + gaxios: 6.5.0(encoding@0.1.13) + gcp-metadata: 6.1.0(encoding@0.1.13) + gtoken: 7.1.0(encoding@0.1.13) jws: 4.0.0 transitivePeerDependencies: - encoding - supports-color - dev: false - /gopd@1.0.1: - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + gopd@1.0.1: dependencies: get-intrinsic: 1.2.4 - /got@11.8.6: - resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} - engines: {node: '>=10.19.0'} + got@11.8.6: dependencies: '@sindresorhus/is': 4.6.0 '@szmarczak/http-timer': 4.0.6 @@ -6536,47 +10494,28 @@ packages: lowercase-keys: 2.0.0 p-cancelable: 2.1.1 responselike: 2.0.1 - dev: false - /graceful-fs@4.2.10: - resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} - dev: true + graceful-fs@4.2.10: {} - /graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + graceful-fs@4.2.11: {} - /graph-data-structure@3.5.0: - resolution: {integrity: sha512-AAgjRtBZC1acIExgK2otv2LDdcYeZdQFKiEStXRDTyaVs6sUUaGUif05pCczTqAU4ny82NQtM1p5PK7AQEYgRA==} - dev: false + graph-data-structure@3.5.0: {} - /grapheme-splitter@1.0.4: - resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} - dev: false + grapheme-splitter@1.0.4: {} - /graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - dev: true + graphemer@1.4.0: {} - /graphql@16.8.1: - resolution: {integrity: sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==} - engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - dev: true + graphql@16.8.1: {} - /gtoken@7.1.0: - resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==} - engines: {node: '>=14.0.0'} + gtoken@7.1.0(encoding@0.1.13): dependencies: - gaxios: 6.3.0 + gaxios: 6.5.0(encoding@0.1.13) jws: 4.0.0 transitivePeerDependencies: - encoding - supports-color - dev: false - /handlebars@4.7.8: - resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} - engines: {node: '>=0.4.7'} - hasBin: true + handlebars@4.7.8: dependencies: minimist: 1.2.8 neo-async: 2.6.2 @@ -6585,565 +10524,363 @@ packages: optionalDependencies: uglify-js: 3.17.4 - /hard-rejection@2.1.0: - resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} - engines: {node: '>=6'} - dev: false + hard-rejection@2.1.0: {} - /has-bigints@1.0.2: - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - dev: true + has-bigints@1.0.2: {} - /has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} + has-flag@3.0.0: {} - /has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} + has-flag@4.0.0: {} - /has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + has-property-descriptors@1.0.2: dependencies: es-define-property: 1.0.0 - /has-proto@1.0.3: - resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} - engines: {node: '>= 0.4'} + has-proto@1.0.3: {} - /has-symbols@1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} - engines: {node: '>= 0.4'} + has-symbols@1.0.3: {} - /has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} + has-tostringtag@1.0.2: dependencies: has-symbols: 1.0.3 - /hasha@5.2.2: - resolution: {integrity: sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==} - engines: {node: '>=8'} + hasha@5.2.2: dependencies: is-stream: 2.0.1 type-fest: 0.8.1 - dev: true - /hasown@2.0.1: - resolution: {integrity: sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==} - engines: {node: '>= 0.4'} + hasown@2.0.2: dependencies: function-bind: 1.1.2 - /he@1.2.0: - resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true - dev: false + he@1.2.0: {} - /hook-std@3.0.0: - resolution: {integrity: sha512-jHRQzjSDzMtFy34AGj1DN+vq54WVuhSvKgrHf0OMiFQTwDD4L/qqofVEWjLOBMTn5+lCD3fPg32W9yOfnEJTTw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true + hook-std@3.0.0: {} - /hosted-git-info@2.8.9: - resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} - dev: false + hosted-git-info@2.8.9: {} - /hosted-git-info@4.1.0: - resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} - engines: {node: '>=10'} + hosted-git-info@4.1.0: dependencies: lru-cache: 6.0.0 - dev: false - /hosted-git-info@7.0.1: - resolution: {integrity: sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==} - engines: {node: ^16.14.0 || >=18.0.0} + hosted-git-info@7.0.2: dependencies: - lru-cache: 10.2.0 - dev: true + lru-cache: 10.2.2 - /html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - dev: true + html-escaper@2.0.2: {} - /http-cache-semantics@4.1.1: - resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} - dev: false + http-cache-semantics@4.1.1: {} - /http-proxy-agent@7.0.2: - resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} - engines: {node: '>= 14'} + http-proxy-agent@7.0.2: dependencies: - agent-base: 7.1.0 + agent-base: 7.1.1 debug: 4.3.4 transitivePeerDependencies: - supports-color - /http2-wrapper@1.0.3: - resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} - engines: {node: '>=10.19.0'} + http2-wrapper@1.0.3: dependencies: quick-lru: 5.1.1 resolve-alpn: 1.2.1 - dev: false - /https-proxy-agent@7.0.4: - resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==} - engines: {node: '>= 14'} + https-proxy-agent@7.0.4: dependencies: - agent-base: 7.1.0 + agent-base: 7.1.1 debug: 4.3.4 transitivePeerDependencies: - supports-color - /human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - dev: true + human-signals@2.1.0: {} - /human-signals@5.0.0: - resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} - engines: {node: '>=16.17.0'} - dev: true + human-signals@5.0.0: {} - /humanize-ms@1.2.1: - resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + humanize-ms@1.2.1: dependencies: ms: 2.1.3 - dev: false - /husky@9.0.11: - resolution: {integrity: sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==} - engines: {node: '>=18'} - hasBin: true - dev: true + husky@9.0.11: {} - /iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - requiresBuild: true + hyperdyperid@1.2.0: {} + + iced-error@0.0.13: {} + + iced-lock@1.1.0: + dependencies: + iced-runtime: 1.0.4 + + iced-lock@2.0.1: + dependencies: + iced-runtime: 1.0.4 + + iced-runtime-3@3.0.5: {} + + iced-runtime@1.0.4: {} + + iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 - dev: false optional: true - /ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - dev: false + ieee754@1.2.1: + optional: true - /ignore@5.3.1: - resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} - engines: {node: '>= 4'} + ignore@5.3.1: {} - /immediate@3.0.6: - resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} - dev: false + immediate@3.0.6: {} - /import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} + import-fresh@3.3.0: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - dev: true - /import-from-esm@1.3.3: - resolution: {integrity: sha512-U3Qt/CyfFpTUv6LOP2jRTLYjphH6zg3okMfHbyqRa/W2w6hr8OsJWVggNlR4jxuojQy81TgTJTxgSkyoteRGMQ==} - engines: {node: '>=16.20'} + import-from-esm@1.3.4: dependencies: debug: 4.3.4 - import-meta-resolve: 4.0.0 + import-meta-resolve: 4.1.0 transitivePeerDependencies: - supports-color - dev: true - /import-in-the-middle@1.7.1: - resolution: {integrity: sha512-1LrZPDtW+atAxH42S6288qyDFNQ2YCty+2mxEPRtfazH6Z5QwkaBSTS2ods7hnVJioF6rkRfNoA6A/MstpFXLg==} + import-in-the-middle@1.7.4: dependencies: acorn: 8.11.3 - acorn-import-assertions: 1.9.0(acorn@8.11.3) - cjs-module-lexer: 1.2.3 + acorn-import-attributes: 1.9.5(acorn@8.11.3) + cjs-module-lexer: 1.3.1 module-details-from-path: 1.0.3 - dev: false - /import-local@3.1.0: - resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} - engines: {node: '>=8'} - hasBin: true + import-local@3.1.0: dependencies: pkg-dir: 4.2.0 resolve-cwd: 3.0.0 - dev: true - /import-meta-resolve@4.0.0: - resolution: {integrity: sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==} - dev: true + import-meta-resolve@4.1.0: {} - /imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} + imurmurhash@0.1.4: {} - /indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} + indent-string@4.0.0: {} - /indent-string@5.0.0: - resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} - engines: {node: '>=12'} - dev: true + indent-string@5.0.0: {} - /index-to-position@0.1.2: - resolution: {integrity: sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==} - engines: {node: '>=18'} - dev: true + index-to-position@0.1.2: {} - /inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + inflight@1.0.6: dependencies: once: 1.4.0 wrappy: 1.0.2 - /inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + inherits@2.0.4: {} - /ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + ini@1.3.8: {} - /ini@4.1.1: - resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - dev: false + ini@4.1.2: {} - /install-artifact-from-github@1.3.5: - resolution: {integrity: sha512-gZHC7f/cJgXz7MXlHFBxPVMsvIbev1OQN1uKQYKVJDydGNm9oYf9JstbU4Atnh/eSvk41WtEovoRm+8IF686xg==} - hasBin: true - requiresBuild: true - dev: false + install-artifact-from-github@1.3.5: optional: true - /internal-slot@1.0.7: - resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} - engines: {node: '>= 0.4'} + internal-slot@1.0.7: dependencies: es-errors: 1.3.0 - hasown: 2.0.1 - side-channel: 1.0.5 - dev: true + hasown: 2.0.2 + side-channel: 1.0.6 - /into-stream@7.0.0: - resolution: {integrity: sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==} - engines: {node: '>=12'} + into-stream@7.0.0: dependencies: from2: 2.3.0 p-is-promise: 3.0.0 - dev: true - /ip-address@9.0.5: - resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} - engines: {node: '>= 12'} - requiresBuild: true + ip-address@9.0.5: dependencies: jsbn: 1.1.0 sprintf-js: 1.1.3 - dev: false optional: true - /is-alphabetical@1.0.4: - resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} - dev: false + is-alphabetical@1.0.4: {} - /is-alphanumerical@1.0.4: - resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==} + is-alphanumerical@1.0.4: dependencies: is-alphabetical: 1.0.4 is-decimal: 1.0.4 - dev: false - /is-arguments@1.1.1: - resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} - engines: {node: '>= 0.4'} + is-arguments@1.1.1: dependencies: call-bind: 1.0.7 has-tostringtag: 1.0.2 - dev: false - /is-array-buffer@3.0.4: - resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} - engines: {node: '>= 0.4'} + is-array-buffer@3.0.4: dependencies: call-bind: 1.0.7 get-intrinsic: 1.2.4 - dev: true - /is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-arrayish@0.2.1: {} - /is-bigint@1.0.4: - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + is-bigint@1.0.4: dependencies: has-bigints: 1.0.2 - dev: true - /is-boolean-object@1.1.2: - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} - engines: {node: '>= 0.4'} + is-boolean-object@1.1.2: dependencies: call-bind: 1.0.7 has-tostringtag: 1.0.2 - dev: true - /is-buffer@2.0.5: - resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} - engines: {node: '>=4'} + is-buffer@2.0.5: {} - /is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} + is-callable@1.2.7: {} - /is-core-module@2.13.1: - resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + is-core-module@2.13.1: dependencies: - hasown: 2.0.1 + hasown: 2.0.2 - /is-date-object@1.0.5: - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} - engines: {node: '>= 0.4'} + is-data-view@1.0.1: + dependencies: + is-typed-array: 1.1.13 + + is-date-object@1.0.5: dependencies: has-tostringtag: 1.0.2 - dev: true - /is-decimal@1.0.4: - resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} - dev: false + is-decimal@1.0.4: {} - /is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} + is-extglob@2.1.1: {} - /is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} + is-fullwidth-code-point@3.0.0: {} - /is-generator-fn@2.1.0: - resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} - engines: {node: '>=6'} - dev: true + is-generator-fn@2.1.0: {} - /is-generator-function@1.0.10: - resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} - engines: {node: '>= 0.4'} + is-generator-function@1.0.10: dependencies: has-tostringtag: 1.0.2 - dev: false - /is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 - /is-hexadecimal@1.0.4: - resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} - dev: false + is-hexadecimal@1.0.4: {} - /is-lambda@1.0.1: - resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} - requiresBuild: true - dev: false + is-lambda@1.0.1: optional: true - /is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} - engines: {node: '>= 0.4'} - dev: true + is-map@2.0.3: {} - /is-number-object@1.0.7: - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} - engines: {node: '>= 0.4'} + is-negative-zero@2.0.3: {} + + is-number-object@1.0.7: dependencies: has-tostringtag: 1.0.2 - dev: true - /is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} + is-number@7.0.0: {} - /is-obj@2.0.0: - resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} - engines: {node: '>=8'} - dev: true + is-obj@2.0.0: {} - /is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - dev: true + is-path-inside@3.0.3: {} - /is-plain-obj@1.1.0: - resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} - engines: {node: '>=0.10.0'} - dev: false + is-plain-obj@1.1.0: {} - /is-plain-obj@2.1.0: - resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} - engines: {node: '>=8'} + is-plain-obj@2.1.0: {} - /is-regex@1.1.4: - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} - engines: {node: '>= 0.4'} + is-regex@1.1.4: dependencies: call-bind: 1.0.7 has-tostringtag: 1.0.2 - dev: true - /is-shared-array-buffer@1.0.3: - resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} - engines: {node: '>= 0.4'} + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.3: dependencies: call-bind: 1.0.7 - dev: true - /is-ssh@1.4.0: - resolution: {integrity: sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==} + is-ssh@1.4.0: dependencies: protocols: 2.0.1 - dev: false - /is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} + is-stream@2.0.1: {} - /is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true + is-stream@3.0.0: {} - /is-string@1.0.7: - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} - engines: {node: '>= 0.4'} + is-string@1.0.7: dependencies: has-tostringtag: 1.0.2 - dev: true - /is-symbol@1.0.4: - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} - engines: {node: '>= 0.4'} + is-symbol@1.0.4: dependencies: has-symbols: 1.0.3 - dev: true - /is-text-path@2.0.0: - resolution: {integrity: sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==} - engines: {node: '>=8'} + is-text-path@2.0.0: dependencies: text-extensions: 2.4.0 - dev: true - /is-typed-array@1.1.13: - resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} - engines: {node: '>= 0.4'} + is-typed-array@1.1.13: dependencies: - which-typed-array: 1.1.14 + which-typed-array: 1.1.15 - /is-typedarray@1.0.0: - resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} + is-typedarray@1.0.0: {} - /is-unicode-supported@2.0.0: - resolution: {integrity: sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==} - engines: {node: '>=18'} - dev: true + is-unicode-supported@2.0.0: {} - /is-weakref@1.0.2: - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + is-weakmap@2.0.2: {} + + is-weakref@1.0.2: dependencies: call-bind: 1.0.7 - dev: true - /is-windows@1.0.2: - resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} - engines: {node: '>=0.10.0'} + is-weakset@2.0.3: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 - /isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + is-windows@1.0.2: {} - /isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - dev: true + isarray@1.0.0: {} - /isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isarray@2.0.5: {} - /isexe@3.1.1: - resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} - engines: {node: '>=16'} - requiresBuild: true - dev: false + isexe@2.0.0: {} + + isexe@3.1.1: optional: true - /issue-parser@6.0.0: - resolution: {integrity: sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA==} - engines: {node: '>=10.13'} + issue-parser@6.0.0: dependencies: lodash.capitalize: 4.2.1 lodash.escaperegexp: 4.1.2 lodash.isplainobject: 4.0.6 lodash.isstring: 4.0.1 lodash.uniqby: 4.7.0 - dev: true - /istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} - dev: true + istanbul-lib-coverage@3.2.2: {} - /istanbul-lib-hook@3.0.0: - resolution: {integrity: sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==} - engines: {node: '>=8'} + istanbul-lib-hook@3.0.0: dependencies: append-transform: 2.0.0 - dev: true - /istanbul-lib-instrument@4.0.3: - resolution: {integrity: sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==} - engines: {node: '>=8'} + istanbul-lib-instrument@4.0.3: dependencies: - '@babel/core': 7.23.9 + '@babel/core': 7.24.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 transitivePeerDependencies: - supports-color - dev: true - /istanbul-lib-instrument@5.2.1: - resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} - engines: {node: '>=8'} + istanbul-lib-instrument@5.2.1: dependencies: - '@babel/core': 7.23.9 - '@babel/parser': 7.23.9 + '@babel/core': 7.24.5 + '@babel/parser': 7.24.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 transitivePeerDependencies: - supports-color - dev: true - /istanbul-lib-instrument@6.0.2: - resolution: {integrity: sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==} - engines: {node: '>=10'} + istanbul-lib-instrument@6.0.2: dependencies: - '@babel/core': 7.23.9 - '@babel/parser': 7.23.9 + '@babel/core': 7.24.5 + '@babel/parser': 7.24.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.5.4 + semver: 7.6.2 transitivePeerDependencies: - supports-color - dev: true - /istanbul-lib-processinfo@2.0.3: - resolution: {integrity: sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==} - engines: {node: '>=8'} + istanbul-lib-processinfo@2.0.3: dependencies: archy: 1.0.0 cross-spawn: 7.0.3 @@ -7151,70 +10888,50 @@ packages: p-map: 3.0.0 rimraf: 3.0.2 uuid: 8.3.2 - dev: true - /istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} + istanbul-lib-report@3.0.1: dependencies: istanbul-lib-coverage: 3.2.2 make-dir: 4.0.0 supports-color: 7.2.0 - dev: true - /istanbul-lib-source-maps@4.0.1: - resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} - engines: {node: '>=10'} + istanbul-lib-source-maps@4.0.1: dependencies: debug: 4.3.4 istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: - supports-color - dev: true - /istanbul-reports@3.1.7: - resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} - engines: {node: '>=8'} + istanbul-reports@3.1.7: dependencies: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 - dev: true - /jackspeak@2.3.6: - resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} - engines: {node: '>=14'} + jackspeak@2.3.6: dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 - /java-properties@1.0.2: - resolution: {integrity: sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==} - engines: {node: '>= 0.6.0'} - dev: true + java-properties@1.0.2: {} - /jest-changed-files@29.7.0: - resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-changed-files@29.7.0: dependencies: execa: 5.1.1 jest-util: 29.7.0 p-limit: 3.1.0 - dev: true - /jest-circus@29.7.0: - resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-circus@29.7.0: dependencies: '@jest/environment': 29.7.0 '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.19.21 + '@types/node': 18.19.33 chalk: 4.1.2 co: 4.6.0 - dedent: 1.5.1 + dedent: 1.5.3 is-generator-fn: 2.1.0 jest-each: 29.7.0 jest-matcher-utils: 29.7.0 @@ -7224,32 +10941,23 @@ packages: jest-util: 29.7.0 p-limit: 3.1.0 pretty-format: 29.7.0 - pure-rand: 6.0.4 + pure-rand: 6.1.0 slash: 3.0.0 stack-utils: 2.0.6 transitivePeerDependencies: - babel-plugin-macros - supports-color - dev: true - /jest-cli@29.7.0(@types/node@18.19.21)(ts-node@10.9.2): - resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true + jest-cli@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@18.19.21)(ts-node@10.9.2) + create-jest: 29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5)) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@18.19.21)(ts-node@10.9.2) + jest-config: 29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -7258,25 +10966,13 @@ packages: - babel-plugin-macros - supports-color - ts-node - dev: true - /jest-config@29.7.0(@types/node@18.19.21)(ts-node@10.9.2): - resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} - 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 + jest-config@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5)): dependencies: - '@babel/core': 7.23.9 + '@babel/core': 7.24.5 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.19.21 - babel-jest: 29.7.0(@babel/core@7.23.9) + babel-jest: 29.7.0(@babel/core@7.24.5) chalk: 4.1.2 ci-info: 3.9.0 deepmerge: 4.3.1 @@ -7295,78 +10991,55 @@ packages: pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 - ts-node: 10.9.2(@swc/core@1.4.2)(@types/node@18.19.21)(typescript@5.3.3) + optionalDependencies: + '@types/node': 18.19.33 + ts-node: 10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5) transitivePeerDependencies: - babel-plugin-macros - supports-color - dev: true - /jest-diff@29.7.0: - resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-diff@29.7.0: dependencies: chalk: 4.1.2 diff-sequences: 29.6.3 jest-get-type: 29.6.3 pretty-format: 29.7.0 - dev: true - /jest-docblock@29.7.0: - resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-docblock@29.7.0: dependencies: detect-newline: 3.1.0 - dev: true - /jest-each@29.7.0: - resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-each@29.7.0: dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 jest-get-type: 29.6.3 jest-util: 29.7.0 pretty-format: 29.7.0 - dev: true - /jest-environment-node@29.7.0: - resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-environment-node@29.7.0: dependencies: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.19.21 + '@types/node': 18.19.33 jest-mock: 29.7.0 jest-util: 29.7.0 - dev: true - /jest-extended@4.0.2(jest@29.7.0): - resolution: {integrity: sha512-FH7aaPgtGYHc9mRjriS0ZEHYM5/W69tLrFTIdzm+yJgeoCmmrSB/luSfMSqWP9O29QWHPEmJ4qmU6EwsZideog==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - jest: '>=27.2.5' - peerDependenciesMeta: - jest: - optional: true + jest-extended@4.0.2(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5))): dependencies: - jest: 29.7.0(@types/node@18.19.21)(ts-node@10.9.2) jest-diff: 29.7.0 jest-get-type: 29.6.3 - dev: true + optionalDependencies: + jest: 29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5)) - /jest-get-type@29.6.3: - resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: true + jest-get-type@29.6.3: {} - /jest-haste-map@29.7.0: - resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-haste-map@29.7.0: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 18.19.21 + '@types/node': 18.19.33 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -7377,41 +11050,29 @@ packages: walker: 1.0.8 optionalDependencies: fsevents: 2.3.3 - dev: true - /jest-leak-detector@29.7.0: - resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-leak-detector@29.7.0: dependencies: jest-get-type: 29.6.3 pretty-format: 29.7.0 - dev: true - /jest-matcher-utils@29.4.1: - resolution: {integrity: sha512-k5h0u8V4nAEy6lSACepxL/rw78FLDkBnXhZVgFneVpnJONhb2DhZj/Gv4eNe+1XqQ5IhgUcqj745UwH0HJmMnA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-matcher-utils@29.4.1: dependencies: chalk: 4.1.2 jest-diff: 29.7.0 jest-get-type: 29.6.3 pretty-format: 29.7.0 - dev: true - /jest-matcher-utils@29.7.0: - resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-matcher-utils@29.7.0: dependencies: chalk: 4.1.2 jest-diff: 29.7.0 jest-get-type: 29.6.3 pretty-format: 29.7.0 - dev: true - /jest-message-util@29.7.0: - resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-message-util@29.7.0: dependencies: - '@babel/code-frame': 7.23.5 + '@babel/code-frame': 7.24.2 '@jest/types': 29.6.3 '@types/stack-utils': 2.0.3 chalk: 4.1.2 @@ -7420,58 +11081,33 @@ packages: pretty-format: 29.7.0 slash: 3.0.0 stack-utils: 2.0.6 - dev: true - /jest-mock-extended@3.0.5(jest@29.7.0)(typescript@5.3.3): - resolution: {integrity: sha512-/eHdaNPUAXe7f65gHH5urc8SbRVWjYxBqmCgax2uqOBJy8UUcCBMN1upj1eZ8y/i+IqpyEm4Kq0VKss/GCCTdw==} - peerDependencies: - jest: ^24.0.0 || ^25.0.0 || ^26.0.0 || ^27.0.0 || ^28.0.0 || ^29.0.0 - typescript: ^3.0.0 || ^4.0.0 || ^5.0.0 + jest-mock-extended@3.0.7(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5)))(typescript@5.4.5): dependencies: - jest: 29.7.0(@types/node@18.19.21)(ts-node@10.9.2) - ts-essentials: 7.0.3(typescript@5.3.3) - typescript: 5.3.3 - dev: true + jest: 29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5)) + ts-essentials: 10.0.0(typescript@5.4.5) + typescript: 5.4.5 - /jest-mock@29.7.0: - resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 18.19.21 + '@types/node': 18.19.33 jest-util: 29.7.0 - dev: true - /jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): - resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} - engines: {node: '>=6'} - peerDependencies: - jest-resolve: '*' - peerDependenciesMeta: - jest-resolve: - optional: true - dependencies: + jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + optionalDependencies: jest-resolve: 29.7.0 - dev: true - /jest-regex-util@29.6.3: - resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: true + jest-regex-util@29.6.3: {} - /jest-resolve-dependencies@29.7.0: - resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-resolve-dependencies@29.7.0: dependencies: jest-regex-util: 29.6.3 jest-snapshot: 29.7.0 transitivePeerDependencies: - supports-color - dev: true - /jest-resolve@29.7.0: - resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-resolve@29.7.0: dependencies: chalk: 4.1.2 graceful-fs: 4.2.11 @@ -7482,18 +11118,15 @@ packages: resolve: 1.22.8 resolve.exports: 2.0.2 slash: 3.0.0 - dev: true - /jest-runner@29.7.0: - resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-runner@29.7.0: 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': 18.19.21 + '@types/node': 18.19.33 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -7511,11 +11144,8 @@ packages: source-map-support: 0.5.13 transitivePeerDependencies: - supports-color - dev: true - /jest-runtime@29.7.0: - resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-runtime@29.7.0: dependencies: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 @@ -7524,9 +11154,9 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.19.21 + '@types/node': 18.19.33 chalk: 4.1.2 - cjs-module-lexer: 1.2.3 + cjs-module-lexer: 1.3.1 collect-v8-coverage: 1.0.2 glob: 7.2.3 graceful-fs: 4.2.11 @@ -7541,21 +11171,18 @@ packages: strip-bom: 4.0.0 transitivePeerDependencies: - supports-color - dev: true - /jest-snapshot@29.7.0: - resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-snapshot@29.7.0: dependencies: - '@babel/core': 7.23.9 - '@babel/generator': 7.23.6 - '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.9) - '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.23.9) - '@babel/types': 7.23.9 + '@babel/core': 7.24.5 + '@babel/generator': 7.24.5 + '@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-syntax-typescript': 7.24.1(@babel/core@7.24.5) + '@babel/types': 7.24.5 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.9) + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.5) chalk: 4.1.2 expect: 29.7.0 graceful-fs: 4.2.11 @@ -7566,26 +11193,20 @@ packages: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.5.4 + semver: 7.6.2 transitivePeerDependencies: - supports-color - dev: true - /jest-util@29.7.0: - resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 18.19.21 + '@types/node': 18.19.33 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 picomatch: 2.3.1 - dev: true - /jest-validate@29.7.0: - resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-validate@29.7.0: dependencies: '@jest/types': 29.6.3 camelcase: 6.3.0 @@ -7593,493 +11214,299 @@ packages: jest-get-type: 29.6.3 leven: 3.1.0 pretty-format: 29.7.0 - dev: true - /jest-watcher@29.7.0: - resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-watcher@29.7.0: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.19.21 + '@types/node': 18.19.33 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 jest-util: 29.7.0 string-length: 4.0.2 - dev: true - /jest-worker@29.7.0: - resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-worker@29.7.0: dependencies: - '@types/node': 18.19.21 + '@types/node': 18.19.33 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - dev: true - /jest@29.7.0(@types/node@18.19.21)(ts-node@10.9.2): - resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true + jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5)) '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@18.19.21)(ts-node@10.9.2) + jest-cli: 29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros - supports-color - ts-node - dev: true - /js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@4.0.0: {} - /js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true + js-yaml@3.14.1: dependencies: argparse: 1.0.10 esprima: 4.0.1 - /js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true + js-yaml@4.1.0: dependencies: argparse: 2.0.1 - /jsbn@1.1.0: - resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} - requiresBuild: true - dev: false + jsbn@1.1.0: optional: true - /jsesc@2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} - hasBin: true - dev: true + jsesc@2.5.2: {} - /json-bigint@1.0.0: - resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + json-bigint@1.0.0: dependencies: bignumber.js: 9.1.2 - dev: false - /json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-buffer@3.0.1: {} - /json-dup-key-validator@1.0.3: - resolution: {integrity: sha512-JvJcV01JSiO7LRz7DY1Fpzn4wX2rJ3dfNTiAfnlvLNdhhnm0Pgdvhi2SGpENrZn7eSg26Ps3TPhOcuD/a4STXQ==} + json-dup-key-validator@1.0.3: dependencies: backslash: 0.2.0 - dev: false - /json-parse-better-errors@1.0.2: - resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} - dev: true + json-parse-better-errors@1.0.2: {} - /json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-parse-even-better-errors@2.3.1: {} - /json-parse-even-better-errors@3.0.1: - resolution: {integrity: sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - dev: true + json-parse-even-better-errors@3.0.2: {} - /json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - dev: true + json-schema-traverse@0.4.1: {} - /json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - dev: true + json-stable-stringify-without-jsonify@1.0.1: {} - /json-stringify-pretty-compact@3.0.0: - resolution: {integrity: sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA==} - dev: false + json-stringify-pretty-compact@3.0.0: {} - /json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + json-stringify-safe@5.0.1: {} - /json5@1.0.2: - resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} - hasBin: true + json5@1.0.2: dependencies: minimist: 1.2.8 - dev: true - /json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true + json5@2.2.3: {} - /jsonata@2.0.3: - resolution: {integrity: sha512-Up2H81MUtjqI/dWwWX7p4+bUMfMrQJVMN/jW6clFMTiYP528fBOBNtRu944QhKTs3+IsVWbgMeUTny5fw2VMUA==} - engines: {node: '>= 8'} - dev: false + jsonata@2.0.5: {} - /jsonc-parser@3.2.0: - resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} - dev: true + jsonc-parser@3.2.1: {} - /jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + jsonfile@6.1.0: dependencies: universalify: 2.0.1 optionalDependencies: graceful-fs: 4.2.11 - /jsonparse@1.3.1: - resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} - engines: {'0': node >= 0.2.0} - dev: true + jsonparse@1.3.1: {} - /just-extend@6.2.0: - resolution: {integrity: sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==} - dev: true + just-extend@6.2.0: {} - /jwa@2.0.0: - resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==} + jwa@2.0.0: dependencies: buffer-equal-constant-time: 1.0.1 ecdsa-sig-formatter: 1.0.11 safe-buffer: 5.2.1 - dev: false - /jws@4.0.0: - resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} + jws@4.0.0: dependencies: jwa: 2.0.0 safe-buffer: 5.2.1 - dev: false - /keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + keybase-ecurve@1.0.1: + dependencies: + bn: 1.0.5 + + keybase-nacl@1.1.4: + dependencies: + iced-runtime: 1.0.4 + tweetnacl: 0.13.3 + uint64be: 1.0.1 + + keyv@4.5.4: dependencies: json-buffer: 3.0.1 - /kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} - dev: false + kind-of@6.0.3: {} - /kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - dev: true + kleur@3.0.3: {} - /klona@2.0.6: - resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} - engines: {node: '>= 8'} - dev: false + klona@2.0.6: {} - /leven@3.1.0: - resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} - engines: {node: '>=6'} - dev: true + leven@3.1.0: {} - /levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 - dev: true - /lie@3.1.1: - resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==} + lie@3.1.1: dependencies: immediate: 3.0.6 - dev: false - - /lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - /linkify-it@4.0.1: - resolution: {integrity: sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==} - dependencies: - uc.micro: 1.0.6 - dev: false + lines-and-columns@1.2.4: {} - /linkify-it@5.0.0: - resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + linkify-it@5.0.0: dependencies: - uc.micro: 2.0.0 - dev: true + uc.micro: 2.1.0 - /load-json-file@4.0.0: - resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} - engines: {node: '>=4'} + load-json-file@4.0.0: dependencies: graceful-fs: 4.2.11 parse-json: 4.0.0 pify: 3.0.0 strip-bom: 3.0.0 - dev: true - /localforage@1.10.0: - resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==} + localforage@1.10.0: dependencies: lie: 3.1.1 - dev: false - /locate-path@2.0.0: - resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} - engines: {node: '>=4'} + locate-path@2.0.0: dependencies: p-locate: 2.0.0 path-exists: 3.0.0 - dev: true - /locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} + locate-path@5.0.0: dependencies: p-locate: 4.1.0 - /locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 - /lodash-es@4.17.21: - resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - dev: true + lodash-es@4.17.21: {} - /lodash.capitalize@4.2.1: - resolution: {integrity: sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==} - dev: true + lodash.capitalize@4.2.1: {} - /lodash.escaperegexp@4.1.2: - resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} - dev: true + lodash.escaperegexp@4.1.2: {} - /lodash.flattendeep@4.4.0: - resolution: {integrity: sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==} - dev: true + lodash.flattendeep@4.4.0: {} - /lodash.get@4.4.2: - resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} - dev: true + lodash.get@4.4.2: {} - /lodash.isplainobject@4.0.6: - resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - dev: true + lodash.isplainobject@4.0.6: {} - /lodash.isstring@4.0.1: - resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} - dev: true + lodash.isstring@4.0.1: {} - /lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - dev: true + lodash.memoize@4.1.2: {} - /lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.merge@4.6.2: {} - /lodash.uniqby@4.7.0: - resolution: {integrity: sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==} - dev: true + lodash.uniqby@4.7.0: {} - /lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lodash@4.17.21: {} - /longest-streak@2.0.4: - resolution: {integrity: sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==} - dev: false + longest-streak@2.0.4: {} - /lowercase-keys@2.0.0: - resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} - engines: {node: '>=8'} - dev: false + lowercase-keys@2.0.0: {} - /lru-cache@10.2.0: - resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} - engines: {node: 14 || >=16.14} + lru-cache@10.2.2: {} - /lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 - dev: true - /lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} + lru-cache@6.0.0: dependencies: yallist: 4.0.0 - /luxon@3.4.4: - resolution: {integrity: sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==} - engines: {node: '>=12'} - dev: false + luxon@3.4.4: {} - /make-dir@3.1.0: - resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} - engines: {node: '>=8'} + make-dir@3.1.0: dependencies: semver: 6.3.1 - dev: true - /make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} + make-dir@4.0.0: dependencies: - semver: 7.5.4 - dev: true + semver: 7.6.2 - /make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - dev: true + make-error@1.3.6: {} - /make-fetch-happen@13.0.0: - resolution: {integrity: sha512-7ThobcL8brtGo9CavByQrQi+23aIfgYU++wg4B87AIS8Rb2ZBt/MEaDqzA00Xwv/jUjAjYkLHjVolYuTLKda2A==} - engines: {node: ^16.14.0 || >=18.0.0} - requiresBuild: true + make-fetch-happen@13.0.1: dependencies: - '@npmcli/agent': 2.2.1 - cacache: 18.0.2 + '@npmcli/agent': 2.2.2 + cacache: 18.0.3 http-cache-semantics: 4.1.1 is-lambda: 1.0.1 - minipass: 7.0.4 - minipass-fetch: 3.0.4 + minipass: 7.1.0 + minipass-fetch: 3.0.5 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 negotiator: 0.6.3 + proc-log: 4.2.0 promise-retry: 2.0.1 - ssri: 10.0.5 + ssri: 10.0.6 transitivePeerDependencies: - supports-color - dev: false optional: true - /makeerror@1.0.12: - resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + makeerror@1.0.12: dependencies: tmpl: 1.0.5 - dev: true - /map-obj@1.0.1: - resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} - engines: {node: '>=0.10.0'} - dev: false + map-obj@1.0.1: {} - /map-obj@4.3.0: - resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} - engines: {node: '>=8'} - dev: false + map-obj@4.3.0: {} - /markdown-it@13.0.2: - resolution: {integrity: sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==} - hasBin: true - dependencies: - argparse: 2.0.1 - entities: 3.0.1 - linkify-it: 4.0.1 - mdurl: 1.0.1 - uc.micro: 1.0.6 - dev: false - - /markdown-it@14.0.0: - resolution: {integrity: sha512-seFjF0FIcPt4P9U39Bq1JYblX0KZCjDLFFQPHpL5AzHpqPEKtosxmdq/LTVZnjfH7tjt9BxStm+wXcDBNuYmzw==} - hasBin: true + markdown-it@14.1.0: dependencies: argparse: 2.0.1 entities: 4.5.0 linkify-it: 5.0.0 mdurl: 2.0.0 punycode.js: 2.3.1 - uc.micro: 2.0.0 - dev: true + uc.micro: 2.1.0 - /markdown-table@2.0.0: - resolution: {integrity: sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==} + markdown-table@2.0.0: dependencies: repeat-string: 1.6.1 - dev: false - /markdownlint-cli2-formatter-default@0.0.4(markdownlint-cli2@0.12.1): - resolution: {integrity: sha512-xm2rM0E+sWgjpPn1EesPXx5hIyrN2ddUnUwnbCsD/ONxYtw3PX6LydvdH6dciWAoFDpwzbHM1TO7uHfcMd6IYg==} - peerDependencies: - markdownlint-cli2: '>=0.0.4' + markdownlint-cli2-formatter-default@0.0.4(markdownlint-cli2@0.13.0): dependencies: - markdownlint-cli2: 0.12.1 - dev: true + markdownlint-cli2: 0.13.0 - /markdownlint-cli2@0.12.1: - resolution: {integrity: sha512-RcK+l5FjJEyrU3REhrThiEUXNK89dLYNJCYbvOUKypxqIGfkcgpz8g08EKqhrmUbYfYoLC5nEYQy53NhJSEtfQ==} - engines: {node: '>=18'} - hasBin: true + markdownlint-cli2@0.13.0: dependencies: - globby: 14.0.0 - jsonc-parser: 3.2.0 - markdownlint: 0.33.0 - markdownlint-cli2-formatter-default: 0.0.4(markdownlint-cli2@0.12.1) + globby: 14.0.1 + js-yaml: 4.1.0 + jsonc-parser: 3.2.1 + markdownlint: 0.34.0 + markdownlint-cli2-formatter-default: 0.0.4(markdownlint-cli2@0.13.0) micromatch: 4.0.5 - yaml: 2.3.4 - dev: true - /markdownlint-micromark@0.1.8: - resolution: {integrity: sha512-1ouYkMRo9/6gou9gObuMDnvZM8jC/ly3QCFQyoSPCS2XV1ZClU0xpKbL1Ar3bWWRT1RnBZkWUEiNKrI2CwiBQA==} - engines: {node: '>=16'} - dev: true + markdownlint-micromark@0.1.9: {} - /markdownlint@0.33.0: - resolution: {integrity: sha512-4lbtT14A3m0LPX1WS/3d1m7Blg+ZwiLq36WvjQqFGsX3Gik99NV+VXp/PW3n+Q62xyPdbvGOCfjPqjW+/SKMig==} - engines: {node: '>=18'} + markdownlint@0.34.0: dependencies: - markdown-it: 14.0.0 - markdownlint-micromark: 0.1.8 - dev: true + markdown-it: 14.1.0 + markdownlint-micromark: 0.1.9 - /marked-terminal@6.2.0(marked@9.1.6): - resolution: {integrity: sha512-ubWhwcBFHnXsjYNsu+Wndpg0zhY4CahSpPlA70PlO0rR9r2sZpkyU+rkCsOWH+KMEkx847UpALON+HWgxowFtw==} - engines: {node: '>=16.0.0'} - peerDependencies: - marked: '>=1 <12' + marked-terminal@6.2.0(marked@9.1.6): dependencies: - ansi-escapes: 6.2.0 + ansi-escapes: 6.2.1 cardinal: 2.1.1 chalk: 5.3.0 - cli-table3: 0.6.3 + cli-table3: 0.6.4 marked: 9.1.6 node-emoji: 2.1.3 supports-hyperlinks: 3.0.0 - dev: true - /marked@9.1.6: - resolution: {integrity: sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==} - engines: {node: '>= 16'} - hasBin: true - dev: true + marked@9.1.6: {} - /matcher@3.0.0: - resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} - engines: {node: '>=10'} + matcher@3.0.0: dependencies: escape-string-regexp: 4.0.0 - dev: false - /mdast-util-find-and-replace@1.1.1: - resolution: {integrity: sha512-9cKl33Y21lyckGzpSmEQnIDjEfeeWelN5s1kUW1LwdB0Fkuq2u+4GdqcGEygYxJE8GVqCl0741bYXHgamfWAZA==} + mdast-util-find-and-replace@1.1.1: dependencies: escape-string-regexp: 4.0.0 unist-util-is: 4.1.0 unist-util-visit-parents: 3.1.1 - dev: false - /mdast-util-from-markdown@0.8.5: - resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==} + mdast-util-from-markdown@0.8.5: dependencies: '@types/mdast': 3.0.15 mdast-util-to-string: 2.0.0 @@ -8088,10 +11515,8 @@ packages: unist-util-stringify-position: 2.0.3 transitivePeerDependencies: - supports-color - dev: false - /mdast-util-to-markdown@0.6.5: - resolution: {integrity: sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==} + mdast-util-to-markdown@0.6.5: dependencies: '@types/unist': 2.0.10 longest-streak: 2.0.4 @@ -8099,44 +11524,25 @@ packages: parse-entities: 2.0.0 repeat-string: 1.6.1 zwitch: 1.0.5 - dev: false - /mdast-util-to-string@1.1.0: - resolution: {integrity: sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==} - dev: false - - /mdast-util-to-string@2.0.0: - resolution: {integrity: sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==} - dev: false + mdast-util-to-string@1.1.0: {} - /mdurl@1.0.1: - resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==} - dev: false + mdast-util-to-string@2.0.0: {} - /mdurl@2.0.0: - resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} - dev: true + mdurl@2.0.0: {} - /memfs@4.7.7: - resolution: {integrity: sha512-x9qc6k88J/VVwnfTkJV8pRRswJ2156Rc4w5rciRqKceFDZ0y1MqsNL9pkg5sE0GOcDzZYbonreALhaHzg1siFw==} - engines: {node: '>= 4.0.0'} + memfs@4.9.2: dependencies: + '@jsonjoy.com/json-pack': 1.0.3(tslib@2.6.2) + '@jsonjoy.com/util': 1.1.2(tslib@2.6.2) + sonic-forest: 1.0.2(tslib@2.6.2) tslib: 2.6.2 - dev: true - /memorystream@0.3.1: - resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} - engines: {node: '>= 0.10.0'} - dev: true + memorystream@0.3.1: {} - /meow@12.1.1: - resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} - engines: {node: '>=16.10'} - dev: true + meow@12.1.1: {} - /meow@7.1.1: - resolution: {integrity: sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA==} - engines: {node: '>=10'} + meow@7.1.1: dependencies: '@types/minimist': 1.2.5 camelcase-keys: 6.2.2 @@ -8149,11 +11555,8 @@ packages: trim-newlines: 3.0.1 type-fest: 0.13.1 yargs-parser: 18.1.3 - dev: false - /meow@8.1.2: - resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} - engines: {node: '>=10'} + meow@8.1.2: dependencies: '@types/minimist': 1.2.5 camelcase-keys: 6.2.2 @@ -8166,523 +11569,273 @@ packages: trim-newlines: 3.0.1 type-fest: 0.18.1 yargs-parser: 20.2.9 - dev: false - /merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - dev: true + merge-stream@2.0.0: {} - /merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} + merge2@1.4.1: {} - /micromark@2.11.4: - resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==} + micromark@2.11.4: dependencies: debug: 4.3.4 parse-entities: 2.0.0 transitivePeerDependencies: - supports-color - dev: false - /micromatch@4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} - engines: {node: '>=8.6'} + micromatch@4.0.5: dependencies: braces: 3.0.2 picomatch: 2.3.1 - /mime@4.0.1: - resolution: {integrity: sha512-5lZ5tyrIfliMXzFtkYyekWbtRXObT9OWa8IwQ5uxTBDHucNNwniRqo0yInflj+iYi5CBa6qxadGzGarDfuEOxA==} - engines: {node: '>=16'} - hasBin: true - dev: true + mime@4.0.3: {} - /mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - dev: true + mimic-fn@2.1.0: {} - /mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - dev: true + mimic-fn@4.0.0: {} - /mimic-response@1.0.1: - resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} - engines: {node: '>=4'} - dev: false + mimic-response@1.0.1: {} - /mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} - dev: false + mimic-response@3.1.0: {} - /min-indent@1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} - dev: false + min-indent@1.0.1: {} - /minimalistic-assert@1.0.1: - resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - dev: false + minimalistic-assert@1.0.1: + optional: true - /minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 - /minimatch@9.0.2: - resolution: {integrity: sha512-PZOT9g5v2ojiTL7r1xF6plNHLtOeTpSlDI007As2NlA2aYBMfVom17yqa6QzhmDP8QOhn7LjHTg7DFCVSSa6yg==} - engines: {node: '>=16 || 14 >=14.17'} + minimatch@9.0.2: dependencies: brace-expansion: 2.0.1 - dev: false - /minimatch@9.0.3: - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} - engines: {node: '>=16 || 14 >=14.17'} + minimatch@9.0.4: dependencies: brace-expansion: 2.0.1 - - /minimist-options@4.1.0: - resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} - engines: {node: '>= 6'} + + minimist-options@4.1.0: dependencies: arrify: 1.0.1 is-plain-obj: 1.1.0 kind-of: 6.0.3 - dev: false - /minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minimist@1.2.8: {} - /minipass-collect@2.0.1: - resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==} - engines: {node: '>=16 || 14 >=14.17'} + minipass-collect@2.0.1: dependencies: - minipass: 7.0.4 - dev: false + minipass: 7.1.0 - /minipass-fetch@3.0.4: - resolution: {integrity: sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - requiresBuild: true + minipass-fetch@3.0.5: dependencies: - minipass: 7.0.4 + minipass: 7.1.0 minipass-sized: 1.0.3 minizlib: 2.1.2 optionalDependencies: encoding: 0.1.13 - dev: false optional: true - /minipass-flush@1.0.5: - resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} - engines: {node: '>= 8'} + minipass-flush@1.0.5: dependencies: minipass: 3.3.6 - dev: false - /minipass-pipeline@1.2.4: - resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} - engines: {node: '>=8'} + minipass-pipeline@1.2.4: dependencies: minipass: 3.3.6 - dev: false - /minipass-sized@1.0.3: - resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} - engines: {node: '>=8'} - requiresBuild: true + minipass-sized@1.0.3: dependencies: minipass: 3.3.6 - dev: false optional: true - /minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} - engines: {node: '>=8'} + minipass@3.3.6: dependencies: yallist: 4.0.0 - /minipass@4.2.8: - resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==} - engines: {node: '>=8'} - dev: true + minipass@4.2.8: {} - /minipass@5.0.0: - resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} - engines: {node: '>=8'} + minipass@5.0.0: {} - /minipass@7.0.4: - resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} - engines: {node: '>=16 || 14 >=14.17'} + minipass@7.1.0: {} - /minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} + minizlib@2.1.2: dependencies: minipass: 3.3.6 yallist: 4.0.0 - /mkdirp-classic@0.5.3: - resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - dev: false + mkdirp-classic@0.5.3: + optional: true - /mkdirp@0.5.6: - resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} - hasBin: true - requiresBuild: true + mkdirp@0.5.6: dependencies: minimist: 1.2.8 - dev: false optional: true - /mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true + mkdirp@1.0.4: {} - /module-details-from-path@1.0.3: - resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} - dev: false + module-details-from-path@1.0.3: {} - /moment@2.30.1: - resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} - requiresBuild: true - dev: false + moment@2.30.1: optional: true - /moo@0.5.2: - resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} - dev: false + moo@0.5.2: {} - /ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + more-entropy@0.0.7: + dependencies: + iced-runtime: 1.0.4 - /ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + ms@2.1.2: {} - /mv@2.1.1: - resolution: {integrity: sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==} - engines: {node: '>=0.8.0'} - requiresBuild: true + ms@2.1.3: {} + + mv@2.1.1: dependencies: mkdirp: 0.5.6 ncp: 2.0.0 rimraf: 2.4.5 - dev: false optional: true - /nan@2.18.0: - resolution: {integrity: sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==} - requiresBuild: true - dev: false + nan@2.19.0: optional: true - /nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - dev: false + nanoid@3.3.7: {} - /napi-build-utils@1.0.2: - resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} - dev: false + napi-build-utils@1.0.2: + optional: true - /natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - dev: true + natural-compare@1.4.0: {} - /ncp@2.0.0: - resolution: {integrity: sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==} - hasBin: true - requiresBuild: true - dev: false + ncp@2.0.0: optional: true - /negotiator@0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} - requiresBuild: true - dev: false + negotiator@0.6.3: optional: true - /neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + neo-async@2.6.2: {} - /nerf-dart@1.0.0: - resolution: {integrity: sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==} - dev: true + nerf-dart@1.0.0: {} - /nise@5.1.9: - resolution: {integrity: sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==} + nise@5.1.9: dependencies: '@sinonjs/commons': 3.0.1 '@sinonjs/fake-timers': 11.2.2 '@sinonjs/text-encoding': 0.7.2 just-extend: 6.2.0 - path-to-regexp: 6.2.1 - dev: true + path-to-regexp: 6.2.2 - /nock@13.5.3: - resolution: {integrity: sha512-2NlGmHIK2rTeyy7UaY1ZNg0YZfEJMxghXgZi0b4DBsUyoDNTTxZeCSG1nmirAWF44RkkoV8NnegLVQijgVapNQ==} - engines: {node: '>= 10.13'} + nock@13.5.4: dependencies: debug: 4.3.4 json-stringify-safe: 5.0.1 propagate: 2.0.1 transitivePeerDependencies: - supports-color - dev: true - /node-abi@3.56.0: - resolution: {integrity: sha512-fZjdhDOeRcaS+rcpve7XuwHBmktS1nS1gzgghwKUQQ8nTy2FdSDr6ZT8k6YhvlJeHmmQMYiT/IH9hfco5zeW2Q==} - engines: {node: '>=10'} + node-abi@3.62.0: dependencies: - semver: 7.5.4 - dev: false + semver: 7.6.2 + optional: true - /node-emoji@2.1.3: - resolution: {integrity: sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==} - engines: {node: '>=18'} + node-emoji@2.1.3: dependencies: '@sindresorhus/is': 4.6.0 char-regex: 1.0.2 emojilib: 2.4.0 skin-tone: 2.0.0 - dev: true - /node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true + node-fetch@2.7.0(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 - dev: false + optionalDependencies: + encoding: 0.1.13 - /node-gyp@10.0.1: - resolution: {integrity: sha512-gg3/bHehQfZivQVfqIyy8wTdSymF9yTyP4CJifK73imyNMU8AIGQE2pUa7dNWfmMeG9cDVF2eehiRMv0LC1iAg==} - engines: {node: ^16.14.0 || >=18.0.0} - hasBin: true - requiresBuild: true + node-gyp@10.1.0: dependencies: env-paths: 2.2.1 exponential-backoff: 3.1.1 - glob: 10.3.10 + glob: 10.3.15 graceful-fs: 4.2.11 - make-fetch-happen: 13.0.0 - nopt: 7.2.0 + make-fetch-happen: 13.0.1 + nopt: 7.2.1 proc-log: 3.0.0 - semver: 7.5.4 - tar: 6.2.0 + semver: 7.6.2 + tar: 6.2.1 which: 4.0.0 transitivePeerDependencies: - supports-color - dev: false optional: true - /node-html-parser@6.1.12: - resolution: {integrity: sha512-/bT/Ncmv+fbMGX96XG9g05vFt43m/+SYKIs9oAemQVYyVcZmDAI2Xq/SbNcpOA35eF0Zk2av3Ksf+Xk8Vt8abA==} + node-html-parser@6.1.13: dependencies: css-select: 5.1.0 he: 1.2.0 - dev: false - /node-int64@0.4.0: - resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - dev: true + node-int64@0.4.0: {} - /node-preload@0.2.1: - resolution: {integrity: sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==} - engines: {node: '>=8'} + node-preload@0.2.1: dependencies: process-on-spawn: 1.0.0 - dev: true - /node-releases@2.0.14: - resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} - dev: true + node-releases@2.0.14: {} - /nopt@7.2.0: - resolution: {integrity: sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - hasBin: true - requiresBuild: true + nopt@7.2.1: dependencies: abbrev: 2.0.0 - dev: false optional: true - /normalize-package-data@2.5.0: - resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 resolve: 1.22.8 semver: 5.7.2 validate-npm-package-license: 3.0.4 - dev: false - /normalize-package-data@3.0.3: - resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} - engines: {node: '>=10'} + normalize-package-data@3.0.3: dependencies: hosted-git-info: 4.1.0 is-core-module: 2.13.1 - semver: 7.5.4 + semver: 7.6.2 validate-npm-package-license: 3.0.4 - dev: false - /normalize-package-data@6.0.0: - resolution: {integrity: sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==} - engines: {node: ^16.14.0 || >=18.0.0} + normalize-package-data@6.0.1: dependencies: - hosted-git-info: 7.0.1 + hosted-git-info: 7.0.2 is-core-module: 2.13.1 - semver: 7.5.4 + semver: 7.6.2 validate-npm-package-license: 3.0.4 - dev: true - /normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - dev: true + normalize-path@3.0.0: {} - /normalize-url@6.1.0: - resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} - engines: {node: '>=10'} - dev: false + normalize-url@6.1.0: {} - /normalize-url@8.0.0: - resolution: {integrity: sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==} - engines: {node: '>=14.16'} - dev: true + normalize-url@8.0.1: {} - /npm-normalize-package-bin@3.0.1: - resolution: {integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - dev: true + npm-normalize-package-bin@3.0.1: {} - /npm-run-all2@6.1.2: - resolution: {integrity: sha512-WwwnS8Ft+RpXve6T2EIEVpFLSqN+ORHRvgNk3H9N62SZXjmzKoRhMFg3I17TK3oMaAEr+XFbRirWS2Fn3BCPSg==} - engines: {node: ^14.18.0 || >=16.0.0, npm: '>= 8'} - hasBin: true + npm-run-all2@6.1.2: dependencies: ansi-styles: 6.2.1 cross-spawn: 7.0.3 memorystream: 0.3.1 - minimatch: 9.0.3 + minimatch: 9.0.4 pidtree: 0.6.0 read-package-json-fast: 3.0.2 shell-quote: 1.8.1 - dev: true - /npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} + npm-run-path@4.0.1: dependencies: path-key: 3.1.1 - dev: true - /npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + npm-run-path@5.3.0: dependencies: path-key: 4.0.0 - dev: true - /npm@10.4.0: - resolution: {integrity: sha512-RS7Mx0OVfXlOcQLRePuDIYdFCVBPCNapWHplDK+mh7GDdP/Tvor4ocuybRRPSvfcRb2vjRJt1fHCqw3cr8qACQ==} - engines: {node: ^18.17.0 || >=20.5.0} - hasBin: true - dev: true - bundledDependencies: - - '@isaacs/string-locale-compare' - - '@npmcli/arborist' - - '@npmcli/config' - - '@npmcli/fs' - - '@npmcli/map-workspaces' - - '@npmcli/package-json' - - '@npmcli/promise-spawn' - - '@npmcli/run-script' - - '@sigstore/tuf' - - abbrev - - archy - - cacache - - chalk - - ci-info - - cli-columns - - cli-table3 - - columnify - - fastest-levenshtein - - fs-minipass - - glob - - graceful-fs - - hosted-git-info - - ini - - init-package-json - - is-cidr - - json-parse-even-better-errors - - libnpmaccess - - libnpmdiff - - libnpmexec - - libnpmfund - - libnpmhook - - libnpmorg - - libnpmpack - - libnpmpublish - - libnpmsearch - - libnpmteam - - libnpmversion - - make-fetch-happen - - minimatch - - minipass - - minipass-pipeline - - ms - - node-gyp - - nopt - - normalize-package-data - - npm-audit-report - - npm-install-checks - - npm-package-arg - - npm-pick-manifest - - npm-profile - - npm-registry-fetch - - npm-user-validate - - npmlog - - p-map - - pacote - - parse-conflict-json - - proc-log - - qrcode-terminal - - read - - semver - - spdx-expression-parse - - ssri - - supports-color - - tar - - text-table - - tiny-relative-date - - treeverse - - validate-npm-package-name - - which - - write-file-atomic + npm@10.7.0: {} - /nth-check@2.1.1: - resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + nth-check@2.1.1: dependencies: boolbase: 1.0.0 - dev: false - /nyc@15.1.0: - resolution: {integrity: sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==} - engines: {node: '>=8.9'} - hasBin: true + nyc@15.1.0: dependencies: '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 @@ -8713,247 +11866,153 @@ packages: yargs: 15.4.1 transitivePeerDependencies: - supports-color - dev: true - /object-inspect@1.13.1: - resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} + object-inspect@1.13.1: {} - /object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} + object-is@1.1.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 - /object.assign@4.1.5: - resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} - engines: {node: '>= 0.4'} + object-keys@1.1.1: {} + + object.assign@4.1.5: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 has-symbols: 1.0.3 object-keys: 1.1.1 - dev: true - /object.fromentries@2.0.7: - resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==} - engines: {node: '>= 0.4'} + object.fromentries@2.0.8: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.4 - dev: true + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 - /object.groupby@1.0.2: - resolution: {integrity: sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==} + object.groupby@1.0.3: dependencies: - array.prototype.filter: 1.0.3 call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.4 - es-errors: 1.3.0 - dev: true + es-abstract: 1.23.3 - /object.values@1.1.7: - resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} - engines: {node: '>= 0.4'} + object.values@1.2.0: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.4 - dev: true + es-object-atoms: 1.0.0 - /once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + once@1.4.0: dependencies: wrappy: 1.0.2 - /onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} + onetime@5.1.2: dependencies: mimic-fn: 2.1.0 - dev: true - /onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} + onetime@6.0.0: dependencies: mimic-fn: 4.0.0 - dev: true - /openpgp@5.11.1: - resolution: {integrity: sha512-TynUBPuaSI7dN0gP+A38CjNRLxkOkkptefNanalDQ71BFAKKm+dLbksymSW5bUrB7RcAneMySL/Y+r/TbLpOnQ==} - engines: {node: '>= 8.0.0'} + openpgp@5.11.1: dependencies: asn1.js: 5.4.1 - dev: false + optional: true - /optionator@0.9.3: - resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} - engines: {node: '>= 0.8.0'} + optionator@0.9.4: dependencies: - '@aashutoshrathi/word-wrap': 1.2.6 deep-is: 0.1.4 fast-levenshtein: 2.0.6 levn: 0.4.1 prelude-ls: 1.2.1 type-check: 0.4.0 - dev: true + word-wrap: 1.2.5 - /p-all@3.0.0: - resolution: {integrity: sha512-qUZbvbBFVXm6uJ7U/WDiO0fv6waBMbjlCm4E66oZdRR+egswICarIdHyVSZZHudH8T5SF8x/JG0q0duFzPnlBw==} - engines: {node: '>=10'} + p-all@3.0.0: dependencies: p-map: 4.0.0 - dev: false - /p-cancelable@2.1.1: - resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} - engines: {node: '>=8'} - dev: false + p-cancelable@2.1.1: {} - /p-each-series@3.0.0: - resolution: {integrity: sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==} - engines: {node: '>=12'} - dev: true + p-each-series@3.0.0: {} - /p-filter@2.1.0: - resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} - engines: {node: '>=8'} + p-filter@2.1.0: dependencies: p-map: 2.1.0 - dev: false - /p-filter@4.1.0: - resolution: {integrity: sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==} - engines: {node: '>=18'} + p-filter@4.1.0: dependencies: - p-map: 7.0.1 - dev: true + p-map: 7.0.2 - /p-finally@1.0.0: - resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} - engines: {node: '>=4'} - dev: false + p-finally@1.0.0: {} - /p-is-promise@3.0.0: - resolution: {integrity: sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==} - engines: {node: '>=8'} - dev: true + p-is-promise@3.0.0: {} - /p-limit@1.3.0: - resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} - engines: {node: '>=4'} + p-limit@1.3.0: dependencies: p-try: 1.0.0 - dev: true - /p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} + p-limit@2.3.0: dependencies: p-try: 2.2.0 - /p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 - /p-locate@2.0.0: - resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} - engines: {node: '>=4'} + p-locate@2.0.0: dependencies: p-limit: 1.3.0 - dev: true - /p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} + p-locate@4.1.0: dependencies: p-limit: 2.3.0 - /p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} + p-locate@5.0.0: dependencies: p-limit: 3.1.0 - /p-map@2.1.0: - resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} - engines: {node: '>=6'} - dev: false + p-map@2.1.0: {} - /p-map@3.0.0: - resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==} - engines: {node: '>=8'} + p-map@3.0.0: dependencies: aggregate-error: 3.1.0 - dev: true - /p-map@4.0.0: - resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} - engines: {node: '>=10'} + p-map@4.0.0: dependencies: aggregate-error: 3.1.0 - dev: false - /p-map@7.0.1: - resolution: {integrity: sha512-2wnaR0XL/FDOj+TgpDuRb2KTjLnu3Fma6b1ZUwGY7LcqenMcvP/YFpjpbPKY6WVGsbuJZRuoUz8iPrt8ORnAFw==} - engines: {node: '>=18'} - dev: true + p-map@7.0.2: {} - /p-queue@6.6.2: - resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} - engines: {node: '>=8'} + p-queue@6.6.2: dependencies: eventemitter3: 4.0.7 p-timeout: 3.2.0 - dev: false - /p-reduce@3.0.0: - resolution: {integrity: sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==} - engines: {node: '>=12'} - dev: true + p-reduce@3.0.0: {} - /p-throttle@4.1.1: - resolution: {integrity: sha512-TuU8Ato+pRTPJoDzYD4s7ocJYcNSEZRvlxoq3hcPI2kZDZ49IQ1Wkj7/gDJc3X7XiEAAvRGtDzdXJI0tC3IL1g==} - engines: {node: '>=10'} - dev: false + p-throttle@4.1.1: {} - /p-timeout@3.2.0: - resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} - engines: {node: '>=8'} + p-timeout@3.2.0: dependencies: p-finally: 1.0.0 - dev: false - /p-try@1.0.0: - resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} - engines: {node: '>=4'} - dev: true + p-try@1.0.0: {} - /p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} + p-try@2.2.0: {} - /package-hash@4.0.0: - resolution: {integrity: sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==} - engines: {node: '>=8'} + package-hash@4.0.0: dependencies: graceful-fs: 4.2.11 hasha: 5.2.2 lodash.flattendeep: 4.4.0 release-zalgo: 1.0.0 - dev: true - /parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} + parent-module@1.0.1: dependencies: callsites: 3.1.0 - dev: true - /parse-entities@2.0.0: - resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} + parse-entities@2.0.0: dependencies: character-entities: 1.2.4 character-entities-legacy: 1.1.4 @@ -8961,356 +12020,224 @@ packages: is-alphanumerical: 1.0.4 is-decimal: 1.0.4 is-hexadecimal: 1.0.4 - dev: false - /parse-json@4.0.0: - resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} - engines: {node: '>=4'} + parse-json@4.0.0: dependencies: error-ex: 1.3.2 json-parse-better-errors: 1.0.2 - dev: true - /parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} + parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.23.5 + '@babel/code-frame': 7.24.2 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 - /parse-json@8.1.0: - resolution: {integrity: sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==} - engines: {node: '>=18'} + parse-json@8.1.0: dependencies: - '@babel/code-frame': 7.23.5 + '@babel/code-frame': 7.24.2 index-to-position: 0.1.2 - type-fest: 4.10.3 - dev: true + type-fest: 4.18.2 - /parse-link-header@2.0.0: - resolution: {integrity: sha512-xjU87V0VyHZybn2RrCX5TIFGxTVZE6zqqZWMPlIKiSKuWh/X5WZdt+w1Ki1nXB+8L/KtL+nZ4iq+sfI6MrhhMw==} + parse-link-header@2.0.0: dependencies: xtend: 4.0.2 - dev: false - /parse-path@7.0.0: - resolution: {integrity: sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==} + parse-path@7.0.0: dependencies: protocols: 2.0.1 - dev: false - /parse-url@8.1.0: - resolution: {integrity: sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==} + parse-url@8.1.0: dependencies: parse-path: 7.0.0 - dev: false - /path-exists@3.0.0: - resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} - engines: {node: '>=4'} - dev: true + path-exists@3.0.0: {} - /path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} + path-exists@4.0.0: {} - /path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} + path-is-absolute@1.0.1: {} - /path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} + path-key@3.1.1: {} - /path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - dev: true + path-key@4.0.0: {} - /path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-parse@1.0.7: {} - /path-scurry@1.10.1: - resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} - engines: {node: '>=16 || 14 >=14.17'} + path-scurry@1.11.1: dependencies: - lru-cache: 10.2.0 - minipass: 7.0.4 + lru-cache: 10.2.2 + minipass: 7.1.0 + + path-to-regexp@6.2.2: {} - /path-to-regexp@6.2.1: - resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} - dev: true + path-type@4.0.0: {} - /path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - dev: true + path-type@5.0.0: {} - /path-type@5.0.0: - resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==} - engines: {node: '>=12'} - dev: true + pend@1.2.0: {} - /pend@1.2.0: - resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - dev: false + pgp-utils@0.0.35: + dependencies: + iced-error: 0.0.13 + iced-runtime: 1.0.4 - /picocolors@1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - dev: true + picocolors@1.0.0: {} - /picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} + picomatch@2.3.1: {} - /pidtree@0.6.0: - resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} - engines: {node: '>=0.10'} - hasBin: true - dev: true + pidtree@0.6.0: {} - /pify@3.0.0: - resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} - engines: {node: '>=4'} - dev: true + pify@3.0.0: {} - /pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} - engines: {node: '>= 6'} - dev: true + pirates@4.0.6: {} - /pkg-conf@2.1.0: - resolution: {integrity: sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==} - engines: {node: '>=4'} + pkg-conf@2.1.0: dependencies: find-up: 2.1.0 load-json-file: 4.0.0 - dev: true - /pkg-dir@4.2.0: - resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} - engines: {node: '>=8'} + pkg-dir@4.2.0: dependencies: find-up: 4.1.0 - dev: true - /possible-typed-array-names@1.0.0: - resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} - engines: {node: '>= 0.4'} + possible-typed-array-names@1.0.0: {} - /prebuild-install@7.1.1: - resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==} - engines: {node: '>=10'} - hasBin: true + prebuild-install@7.1.2: dependencies: - detect-libc: 2.0.2 + detect-libc: 2.0.3 expand-template: 2.0.3 github-from-package: 0.0.0 minimist: 1.2.8 mkdirp-classic: 0.5.3 napi-build-utils: 1.0.2 - node-abi: 3.56.0 + node-abi: 3.62.0 pump: 3.0.0 rc: 1.2.8 simple-get: 4.0.1 tar-fs: 2.1.1 tunnel-agent: 0.6.0 - dev: false + optional: true - /prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - dev: true + prelude-ls@1.2.1: {} - /prettier@3.2.5: - resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} - engines: {node: '>=14'} - hasBin: true - dev: false + prettier@3.2.5: {} - /pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + pretty-format@29.7.0: dependencies: '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 - react-is: 18.2.0 - dev: true + react-is: 18.3.1 - /proc-log@3.0.0: - resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - requiresBuild: true - dev: false + proc-log@3.0.0: optional: true - /process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + proc-log@4.2.0: + optional: true - /process-on-spawn@1.0.0: - resolution: {integrity: sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==} - engines: {node: '>=8'} + process-nextick-args@2.0.1: {} + + process-on-spawn@1.0.0: dependencies: fromentries: 1.3.2 - dev: true - /promise-retry@2.0.1: - resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} - engines: {node: '>=10'} - requiresBuild: true + progress@1.1.8: {} + + promise-retry@2.0.1: dependencies: err-code: 2.0.3 retry: 0.12.0 - dev: false optional: true - /prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} + prompts@2.4.2: dependencies: kleur: 3.0.3 sisteransi: 1.0.5 - dev: true - /propagate@2.0.1: - resolution: {integrity: sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==} - engines: {node: '>= 8'} - dev: true + propagate@2.0.1: {} - /proto-list@1.2.4: - resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} - dev: true + proto-list@1.2.4: {} - /protocols@2.0.1: - resolution: {integrity: sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==} - dev: false + protocols@2.0.1: {} - /pump@3.0.0: - resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + pump@3.0.0: dependencies: end-of-stream: 1.4.4 once: 1.4.0 - dev: false - /punycode.js@2.3.1: - resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} - engines: {node: '>=6'} - dev: true + punycode.js@2.3.1: {} - /punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - dev: true + punycode@2.3.1: {} - /pure-rand@6.0.4: - resolution: {integrity: sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==} - dev: true + pure-rand@6.1.0: {} - /qs@6.11.2: - resolution: {integrity: sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==} - engines: {node: '>=0.6'} + purepack@1.0.6: {} + + qs@6.12.1: dependencies: - side-channel: 1.0.5 - dev: false + side-channel: 1.0.6 - /queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + queue-microtask@1.2.3: {} - /quick-lru@4.0.1: - resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} - engines: {node: '>=8'} - dev: false + quick-lru@4.0.1: {} - /quick-lru@5.1.1: - resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} - engines: {node: '>=10'} - dev: false + quick-lru@5.1.1: {} - /rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true + rc@1.2.8: dependencies: deep-extend: 0.6.0 ini: 1.3.8 minimist: 1.2.8 strip-json-comments: 2.0.1 - /re2@1.20.9: - resolution: {integrity: sha512-ZYcPTFr5ha2xq3WQjBDTF9CWPSDK1z28MLh5UFRxc//7X8BNQ3A7yR7ITnP0jO346661ertdKVFqw1qoL3FMEQ==} - requiresBuild: true + re2@1.20.11: dependencies: install-artifact-from-github: 1.3.5 - nan: 2.18.0 - node-gyp: 10.0.1 + nan: 2.19.0 + node-gyp: 10.1.0 transitivePeerDependencies: - supports-color - dev: false optional: true - /react-is@18.2.0: - resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - dev: true + react-is@18.3.1: {} - /read-package-json-fast@3.0.2: - resolution: {integrity: sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + read-package-json-fast@3.0.2: dependencies: - json-parse-even-better-errors: 3.0.1 + json-parse-even-better-errors: 3.0.2 npm-normalize-package-bin: 3.0.1 - dev: true - /read-pkg-up@11.0.0: - resolution: {integrity: sha512-LOVbvF1Q0SZdjClSefZ0Nz5z8u+tIE7mV5NibzmE9VYmDe9CaBbAVtz1veOSZbofrdsilxuDAYnFenukZVp8/Q==} - engines: {node: '>=18'} - deprecated: Renamed to read-package-up + read-pkg-up@11.0.0: dependencies: find-up-simple: 1.0.0 read-pkg: 9.0.1 - type-fest: 4.10.3 - dev: true + type-fest: 4.18.2 - /read-pkg-up@7.0.1: - resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} - engines: {node: '>=8'} + read-pkg-up@7.0.1: dependencies: find-up: 4.1.0 read-pkg: 5.2.0 type-fest: 0.8.1 - dev: false - /read-pkg@5.2.0: - resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} - engines: {node: '>=8'} + read-pkg@5.2.0: dependencies: '@types/normalize-package-data': 2.4.4 normalize-package-data: 2.5.0 parse-json: 5.2.0 type-fest: 0.6.0 - dev: false - /read-pkg@9.0.1: - resolution: {integrity: sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==} - engines: {node: '>=18'} + read-pkg@9.0.1: dependencies: '@types/normalize-package-data': 2.4.4 - normalize-package-data: 6.0.0 + normalize-package-data: 6.0.1 parse-json: 8.1.0 - type-fest: 4.10.3 + type-fest: 4.18.2 unicorn-magic: 0.1.0 - dev: true - /read-yaml-file@2.1.0: - resolution: {integrity: sha512-UkRNRIwnhG+y7hpqnycCL/xbTk7+ia9VuVTC0S+zVbwd65DI9eUpRMfsWIGrCWxTU/mi+JW8cHQCrv+zfCbEPQ==} - engines: {node: '>=10.13'} + read-yaml-file@2.1.0: dependencies: js-yaml: 4.1.0 strip-bom: 4.0.0 - dev: false - /readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 inherits: 2.0.4 @@ -9320,31 +12247,22 @@ packages: string_decoder: 1.1.1 util-deprecate: 1.0.2 - /readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} + readable-stream@3.6.2: dependencies: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 - dev: false - /redent@3.0.0: - resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} - engines: {node: '>=8'} + redent@3.0.0: dependencies: indent-string: 4.0.0 strip-indent: 3.0.0 - dev: false - /redeyed@2.1.1: - resolution: {integrity: sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==} + redeyed@2.1.1: dependencies: esprima: 4.0.1 - dev: true - /redis@4.6.13: - resolution: {integrity: sha512-MHgkS4B+sPjCXpf+HfdetBwbRz6vCtsceTmw1pHNYJAsYxrfpOP6dz+piJWGos8wqG7qb3vj/Rrc5qOlmInUuA==} + redis@4.6.13: dependencies: '@redis/bloom': 1.2.0(@redis/client@1.5.14) '@redis/client': 1.5.14 @@ -9352,256 +12270,163 @@ packages: '@redis/json': 1.0.6(@redis/client@1.5.14) '@redis/search': 1.1.6(@redis/client@1.5.14) '@redis/time-series': 1.0.5(@redis/client@1.5.14) - dev: false - /regenerator-runtime@0.14.1: - resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - dev: false + regenerator-runtime@0.14.1: {} - /regexp.prototype.flags@1.5.2: - resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} - engines: {node: '>= 0.4'} + regexp.prototype.flags@1.5.2: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-errors: 1.3.0 set-function-name: 2.0.2 - dev: true - /registry-auth-token@5.0.2: - resolution: {integrity: sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==} - engines: {node: '>=14'} + registry-auth-token@5.0.2: dependencies: '@pnpm/npm-conf': 2.2.2 - dev: true - /release-zalgo@1.0.0: - resolution: {integrity: sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==} - engines: {node: '>=4'} + release-zalgo@1.0.0: dependencies: es6-error: 4.1.1 - dev: true - /remark-github@10.1.0: - resolution: {integrity: sha512-q0BTFb41N6/uXQVkxRwLRTFRfLFPYP+8li26Js5XC0GKritCSaxrftd+t+8sfN+1i9BtmJPUKoS7CZwtccj0Fg==} + remark-github@10.1.0: dependencies: mdast-util-find-and-replace: 1.1.1 mdast-util-to-string: 1.1.0 unist-util-visit: 2.0.3 - dev: false - /remark-parse@9.0.0: - resolution: {integrity: sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==} + remark-parse@9.0.0: dependencies: mdast-util-from-markdown: 0.8.5 transitivePeerDependencies: - supports-color - dev: false - /remark-stringify@9.0.1: - resolution: {integrity: sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg==} + remark-stringify@9.0.1: dependencies: mdast-util-to-markdown: 0.6.5 - dev: false - /remark@13.0.0: - resolution: {integrity: sha512-HDz1+IKGtOyWN+QgBiAT0kn+2s6ovOxHyPAFGKVE81VSzJ+mq7RwHFledEvB5F1p4iJvOah/LOKdFuzvRnNLCA==} + remark@13.0.0: dependencies: remark-parse: 9.0.0 remark-stringify: 9.0.1 unified: 9.2.2 transitivePeerDependencies: - supports-color - dev: false - /repeat-string@1.6.1: - resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} - engines: {node: '>=0.10'} - dev: false + repeat-string@1.6.1: {} - /require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - dev: true + require-directory@2.1.1: {} - /require-in-the-middle@7.2.0: - resolution: {integrity: sha512-3TLx5TGyAY6AOqLBoXmHkNql0HIf2RGbuMgCDT2WO/uGVAPJs6h7Kl+bN6TIZGd9bWhWPwnDnTHGtW8Iu77sdw==} - engines: {node: '>=8.6.0'} + require-in-the-middle@7.3.0: dependencies: debug: 4.3.4 module-details-from-path: 1.0.3 resolve: 1.22.8 transitivePeerDependencies: - supports-color - dev: false - /require-main-filename@2.0.0: - resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - dev: true + require-main-filename@2.0.0: {} - /resolve-alpn@1.2.1: - resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} - dev: false + resolve-alpn@1.2.1: {} - /resolve-cwd@3.0.0: - resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} - engines: {node: '>=8'} + resolve-cwd@3.0.0: dependencies: resolve-from: 5.0.0 - dev: true - /resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - dev: true + resolve-from@4.0.0: {} - /resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - dev: true + resolve-from@5.0.0: {} - /resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - dev: true + resolve-pkg-maps@1.0.0: {} - /resolve.exports@2.0.2: - resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} - engines: {node: '>=10'} - dev: true + resolve.exports@2.0.2: {} - /resolve@1.22.8: - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} - hasBin: true + resolve@1.22.8: dependencies: is-core-module: 2.13.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - /responselike@2.0.1: - resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} + responselike@2.0.1: dependencies: lowercase-keys: 2.0.0 - dev: false - /retry@0.12.0: - resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} - engines: {node: '>= 4'} - requiresBuild: true - dev: false + retry@0.12.0: optional: true - /reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + reusify@1.0.4: {} - /rimraf@2.4.5: - resolution: {integrity: sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==} - hasBin: true - requiresBuild: true + rimraf@2.4.5: dependencies: glob: 6.0.4 - dev: false optional: true - /rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - hasBin: true + rimraf@3.0.2: dependencies: glob: 7.2.3 - dev: true - /rimraf@5.0.5: - resolution: {integrity: sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==} - engines: {node: '>=14'} - hasBin: true + rimraf@5.0.7: dependencies: - glob: 10.3.10 - dev: true + glob: 10.3.15 - /roarr@2.15.4: - resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} - engines: {node: '>=8.0'} + roarr@2.15.4: dependencies: boolean: 3.2.0 detect-node: 2.1.0 - globalthis: 1.0.3 + globalthis: 1.0.4 json-stringify-safe: 5.0.1 semver-compare: 1.0.0 sprintf-js: 1.1.3 - dev: false - /run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 - /safe-array-concat@1.1.0: - resolution: {integrity: sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==} - engines: {node: '>=0.4'} + safe-array-concat@1.1.2: dependencies: call-bind: 1.0.7 get-intrinsic: 1.2.4 has-symbols: 1.0.3 isarray: 2.0.5 - dev: true - /safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safe-buffer@5.1.2: {} - /safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - dev: false + safe-buffer@5.2.1: {} - /safe-json-stringify@1.2.0: - resolution: {integrity: sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==} - requiresBuild: true - dev: false + safe-json-stringify@1.2.0: optional: true - /safe-regex-test@1.0.3: - resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} - engines: {node: '>= 0.4'} + safe-regex-test@1.0.3: dependencies: call-bind: 1.0.7 es-errors: 1.3.0 is-regex: 1.1.4 - dev: true - /safe-stable-stringify@2.4.3: - resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==} - engines: {node: '>=10'} - dev: false + safe-stable-stringify@2.4.3: {} - /safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - dev: false + safer-buffer@2.1.2: + optional: true - /sax@1.3.0: - resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==} - dev: false + sax@1.3.0: {} - /semantic-release@22.0.12(typescript@5.3.3): - resolution: {integrity: sha512-0mhiCR/4sZb00RVFJIUlMuiBkW3NMpVIW2Gse7noqEMoFGkvfPPAImEQbkBV8xga4KOPP4FdTRYuLLy32R1fPw==} - engines: {node: ^18.17 || >=20.6.1} - hasBin: true + semantic-release@22.0.12(typescript@5.4.5): dependencies: - '@semantic-release/commit-analyzer': 11.1.0(semantic-release@22.0.12) + '@semantic-release/commit-analyzer': 11.1.0(semantic-release@22.0.12(typescript@5.4.5)) '@semantic-release/error': 4.0.0 - '@semantic-release/github': 9.2.6(semantic-release@22.0.12) - '@semantic-release/npm': 11.0.2(semantic-release@22.0.12) - '@semantic-release/release-notes-generator': 12.1.0(semantic-release@22.0.12) + '@semantic-release/github': 9.2.6(semantic-release@22.0.12(typescript@5.4.5)) + '@semantic-release/npm': 11.0.3(semantic-release@22.0.12(typescript@5.4.5)) + '@semantic-release/release-notes-generator': 12.1.0(semantic-release@22.0.12(typescript@5.4.5)) aggregate-error: 5.0.0 - cosmiconfig: 8.3.6(typescript@5.3.3) + cosmiconfig: 8.3.6(typescript@5.4.5) debug: 4.3.4 env-ci: 10.0.0 execa: 8.0.1 - figures: 6.0.1 + figures: 6.1.0 find-versions: 5.1.0 get-stream: 6.0.1 git-log-parser: 1.2.0 hook-std: 3.0.0 - hosted-git-info: 7.0.1 - import-from-esm: 1.3.3 + hosted-git-info: 7.0.2 + import-from-esm: 1.3.4 lodash-es: 4.17.21 marked: 9.1.6 marked-terminal: 6.2.0(marked@9.1.6) @@ -9610,72 +12435,41 @@ packages: p-reduce: 3.0.0 read-pkg-up: 11.0.0 resolve-from: 5.0.0 - semver: 7.5.4 + semver: 7.6.2 semver-diff: 4.0.0 signale: 1.4.0 yargs: 17.7.2 transitivePeerDependencies: - supports-color - typescript - dev: true - /semver-compare@1.0.0: - resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} - dev: false + semver-compare@1.0.0: {} - /semver-diff@4.0.0: - resolution: {integrity: sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==} - engines: {node: '>=12'} + semver-diff@4.0.0: dependencies: - semver: 7.5.4 - dev: true + semver: 7.6.2 - /semver-regex@4.0.5: - resolution: {integrity: sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==} - engines: {node: '>=12'} - dev: true + semver-regex@4.0.5: {} - /semver-stable@3.0.0: - resolution: {integrity: sha512-lolq9k0lqdnZ0C4+QJvrPBWiAHGhLaVSMTPJajn527ptOHAcZnEhj0n2r6ALnryNiRKPO9AIO9iBI3ZSheHCaw==} - engines: {node: '>=0.10.0'} + semver-stable@3.0.0: dependencies: semver: 6.3.1 - dev: false - /semver-utils@1.1.4: - resolution: {integrity: sha512-EjnoLE5OGmDAVV/8YDoN5KiajNadjzIp9BAHOhYeQHt7j0UWxjmgsx4YD48wp4Ue1Qogq38F1GNUJNqF1kKKxA==} - dev: false + semver-utils@1.1.4: {} - /semver@5.7.2: - resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} - hasBin: true - dev: false + semver@5.7.2: {} - /semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true + semver@6.3.1: {} - /semver@7.5.4: - resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} - engines: {node: '>=10'} - hasBin: true - dependencies: - lru-cache: 6.0.0 + semver@7.6.2: {} - /serialize-error@7.0.1: - resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} - engines: {node: '>=10'} + serialize-error@7.0.1: dependencies: type-fest: 0.13.1 - dev: false - /set-blocking@2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - dev: true + set-blocking@2.0.0: {} - /set-function-length@1.2.1: - resolution: {integrity: sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==} - engines: {node: '>= 0.4'} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 es-errors: 1.3.0 @@ -9684,87 +12478,61 @@ packages: gopd: 1.0.1 has-property-descriptors: 1.0.2 - /set-function-name@2.0.2: - resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} - engines: {node: '>= 0.4'} + set-function-name@2.0.2: dependencies: define-data-property: 1.1.4 es-errors: 1.3.0 functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 - dev: true - /shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 - /shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} + shebang-regex@3.0.0: {} - /shell-quote@1.8.1: - resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} - dev: true + shell-quote@1.8.1: {} - /shimmer@1.2.1: - resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} - dev: false + shimmer@1.2.1: {} - /shlex@2.1.2: - resolution: {integrity: sha512-Nz6gtibMVgYeMEhUjp2KuwAgqaJA1K155dU/HuDaEJUGgnmYfVtVZah+uerVWdH8UGnyahhDCgABbYTbs254+w==} - dev: false + shlex@2.1.2: {} - /side-channel@1.0.5: - resolution: {integrity: sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==} - engines: {node: '>= 0.4'} + side-channel@1.0.6: dependencies: call-bind: 1.0.7 es-errors: 1.3.0 get-intrinsic: 1.2.4 object-inspect: 1.13.1 - /signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + signal-exit@3.0.7: {} - /signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} + signal-exit@4.1.0: {} - /signale@1.4.0: - resolution: {integrity: sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==} - engines: {node: '>=6'} + signale@1.4.0: dependencies: chalk: 2.4.2 figures: 2.0.0 pkg-conf: 2.1.0 - dev: true - /simple-concat@1.0.1: - resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - dev: false + simple-concat@1.0.1: + optional: true - /simple-get@4.0.1: - resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + simple-get@4.0.1: dependencies: decompress-response: 6.0.0 once: 1.4.0 simple-concat: 1.0.1 - dev: false + optional: true - /simple-git@3.22.0: - resolution: {integrity: sha512-6JujwSs0ac82jkGjMHiCnTifvf1crOiY/+tfs/Pqih6iow7VrpNKRRNdWm6RtaXpvvv/JGNYhlUtLhGFqHF+Yw==} + simple-git@3.24.0: dependencies: '@kwsites/file-exists': 1.1.1 '@kwsites/promise-deferred': 1.1.1 debug: 4.3.4 transitivePeerDependencies: - supports-color - dev: false - /sinon@16.1.3: - resolution: {integrity: sha512-mjnWWeyxcAf9nC0bXcPmiDut+oE8HYridTNzBbF98AYVLmWwGRp2ISEpyhYflG1ifILT+eNn3BmKUJPxjXUPlA==} + sinon@16.1.3: dependencies: '@sinonjs/commons': 3.0.1 '@sinonjs/fake-timers': 10.3.0 @@ -9772,96 +12540,61 @@ packages: diff: 5.2.0 nise: 5.1.9 supports-color: 7.2.0 - dev: true - /sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - dev: true + sisteransi@1.0.5: {} - /skin-tone@2.0.0: - resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==} - engines: {node: '>=8'} + skin-tone@2.0.0: dependencies: unicode-emoji-modifier-base: 1.0.0 - dev: true - /slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - dev: true + slash@3.0.0: {} - /slash@5.1.0: - resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} - engines: {node: '>=14.16'} - dev: true + slash@5.1.0: {} - /slugify@1.6.6: - resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==} - engines: {node: '>=8.0.0'} - dev: false + slugify@1.6.6: {} - /smart-buffer@4.2.0: - resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} - engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - requiresBuild: true - dev: false + smart-buffer@4.2.0: optional: true - /socks-proxy-agent@8.0.2: - resolution: {integrity: sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==} - engines: {node: '>= 14'} - requiresBuild: true + socks-proxy-agent@8.0.3: dependencies: - agent-base: 7.1.0 + agent-base: 7.1.1 debug: 4.3.4 - socks: 2.8.1 + socks: 2.8.3 transitivePeerDependencies: - supports-color - dev: false optional: true - /socks@2.8.1: - resolution: {integrity: sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ==} - engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - requiresBuild: true + socks@2.8.3: dependencies: ip-address: 9.0.5 smart-buffer: 4.2.0 - dev: false optional: true - /sort-keys@4.2.0: - resolution: {integrity: sha512-aUYIEU/UviqPgc8mHR6IW1EGxkAXpeRETYcrzg8cLAvUPZcpAlleSXHV2mY7G12GphSH6Gzv+4MMVSSkbdteHg==} - engines: {node: '>=8'} + sonic-forest@1.0.2(tslib@2.6.2): + dependencies: + tree-dump: 1.0.1(tslib@2.6.2) + tslib: 2.6.2 + + sort-keys@4.2.0: dependencies: is-plain-obj: 2.1.0 - dev: false - /source-map-support@0.5.13: - resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + source-map-support@0.5.13: dependencies: buffer-from: 1.1.2 source-map: 0.6.1 - dev: true - /source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 source-map: 0.6.1 - dev: false - /source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} + source-map@0.6.1: {} - /spawn-error-forwarder@1.0.0: - resolution: {integrity: sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==} - dev: true + spawn-error-forwarder@1.0.0: {} - /spawn-wrap@2.0.0: - resolution: {integrity: sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==} - engines: {node: '>=8'} + spawn-wrap@2.0.0: dependencies: foreground-child: 2.0.0 is-windows: 1.0.2 @@ -9869,245 +12602,163 @@ packages: rimraf: 3.0.2 signal-exit: 3.0.7 which: 2.0.2 - dev: true - /spdx-correct@3.2.0: - resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 spdx-license-ids: 3.0.17 - /spdx-exceptions@2.5.0: - resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + spdx-exceptions@2.5.0: {} - /spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + spdx-expression-parse@3.0.1: dependencies: spdx-exceptions: 2.5.0 spdx-license-ids: 3.0.17 - /spdx-license-ids@3.0.17: - resolution: {integrity: sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==} + spdx-license-ids@3.0.17: {} - /split2@1.0.0: - resolution: {integrity: sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==} + split2@1.0.0: dependencies: through2: 2.0.5 - dev: true - /split2@3.2.2: - resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} + split2@3.2.2: dependencies: readable-stream: 3.6.2 - dev: false - /split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} - dev: true + split2@4.2.0: {} - /sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + sprintf-js@1.0.3: {} - /sprintf-js@1.1.3: - resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} - dev: false + sprintf-js@1.1.3: {} - /ssri@10.0.5: - resolution: {integrity: sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + ssri@10.0.6: dependencies: - minipass: 7.0.4 - dev: false + minipass: 7.1.0 - /stack-utils@2.0.6: - resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} - engines: {node: '>=10'} + stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 - dev: true - /stream-combiner2@1.1.1: - resolution: {integrity: sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==} + stop-iteration-iterator@1.0.0: + dependencies: + internal-slot: 1.0.7 + + stream-combiner2@1.1.1: dependencies: duplexer2: 0.1.4 readable-stream: 2.3.8 - dev: true - /string-length@4.0.2: - resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} - engines: {node: '>=10'} + string-length@4.0.2: dependencies: char-regex: 1.0.2 strip-ansi: 6.0.1 - dev: true - /string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - /string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} + string-width@5.1.2: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.1.0 - /string.prototype.trim@1.2.8: - resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} - engines: {node: '>= 0.4'} + string.prototype.trim@1.2.9: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.4 - dev: true + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 - /string.prototype.trimend@1.0.7: - resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==} + string.prototype.trimend@1.0.8: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.4 - dev: true + es-object-atoms: 1.0.0 - /string.prototype.trimstart@1.0.7: - resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} + string.prototype.trimstart@1.0.8: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.4 - dev: true + es-object-atoms: 1.0.0 - /string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 - /string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 - dev: false - /strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 - /strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} + strip-ansi@7.1.0: dependencies: ansi-regex: 6.0.1 - /strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - dev: true + strip-bom@3.0.0: {} - /strip-bom@4.0.0: - resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} - engines: {node: '>=8'} + strip-bom@4.0.0: {} - /strip-comments-strings@1.2.0: - resolution: {integrity: sha512-zwF4bmnyEjZwRhaak9jUWNxc0DoeKBJ7lwSN/LEc8dQXZcUFG6auaaTQJokQWXopLdM3iTx01nQT8E4aL29DAQ==} - dev: false + strip-comments-strings@1.2.0: {} - /strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - dev: true + strip-final-newline@2.0.0: {} - /strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} - dev: true + strip-final-newline@3.0.0: {} - /strip-indent@3.0.0: - resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} - engines: {node: '>=8'} + strip-indent@3.0.0: dependencies: min-indent: 1.0.1 - dev: false - /strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} + strip-json-comments@2.0.1: {} - /strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - dev: true + strip-json-comments@3.1.1: {} - /strnum@1.0.5: - resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} - dev: false + strnum@1.0.5: {} - /supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} + supports-color@5.5.0: dependencies: has-flag: 3.0.0 - /supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 - /supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} + supports-color@8.1.1: dependencies: has-flag: 4.0.0 - dev: true - /supports-hyperlinks@3.0.0: - resolution: {integrity: sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==} - engines: {node: '>=14.18'} + supports-hyperlinks@3.0.0: dependencies: has-flag: 4.0.0 supports-color: 7.2.0 - dev: true - /supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} + supports-preserve-symlinks-flag@1.0.0: {} - /tapable@2.2.1: - resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} - engines: {node: '>=6'} - dev: true + tapable@2.2.1: {} - /tar-fs@2.1.1: - resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} + tar-fs@2.1.1: dependencies: chownr: 1.1.4 mkdirp-classic: 0.5.3 pump: 3.0.0 tar-stream: 2.2.0 - dev: false + optional: true - /tar-stream@2.2.0: - resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} - engines: {node: '>=6'} + tar-stream@2.2.0: dependencies: bl: 4.1.0 end-of-stream: 1.4.4 fs-constants: 1.0.0 inherits: 2.0.4 readable-stream: 3.6.2 - dev: false + optional: true - /tar@6.2.0: - resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==} - engines: {node: '>=10'} + tar@6.2.1: dependencies: chownr: 2.0.0 fs-minipass: 2.1.0 @@ -10116,328 +12767,204 @@ packages: mkdirp: 1.0.4 yallist: 4.0.0 - /temp-dir@3.0.0: - resolution: {integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==} - engines: {node: '>=14.16'} - dev: true + temp-dir@3.0.0: {} - /tempy@3.1.0: - resolution: {integrity: sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==} - engines: {node: '>=14.16'} + tempy@3.1.0: dependencies: is-stream: 3.0.0 temp-dir: 3.0.0 type-fest: 2.19.0 unique-string: 3.0.0 - dev: true - /test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} - engines: {node: '>=8'} + test-exclude@6.0.0: dependencies: '@istanbuljs/schema': 0.1.3 glob: 7.2.3 minimatch: 3.1.2 - dev: true - /text-extensions@2.4.0: - resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} - engines: {node: '>=8'} - dev: true + text-extensions@2.4.0: {} - /text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - dev: true + text-table@0.2.0: {} - /through2-concurrent@2.0.0: - resolution: {integrity: sha512-R5/jLkfMvdmDD+seLwN7vB+mhbqzWop5fAjx5IX8/yQq7VhBhzDmhXgaHAOnhnWkCpRMM7gToYHycB0CS/pd+A==} + thingies@1.21.0(tslib@2.6.2): + dependencies: + tslib: 2.6.2 + + through2-concurrent@2.0.0: dependencies: through2: 2.0.5 - dev: false - /through2@2.0.5: - resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + through2@2.0.5: dependencies: readable-stream: 2.3.8 xtend: 4.0.2 - /through2@4.0.2: - resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + through2@4.0.2: dependencies: readable-stream: 3.6.2 - dev: false - /through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - dev: true + through@2.3.8: {} - /tinylogic@2.0.0: - resolution: {integrity: sha512-dljTkiLLITtsjqBvTA1MRZQK/sGP4kI3UJKc3yA9fMzYbMF2RhcN04SeROVqJBIYYOoJMM8u0WDnhFwMSFQotw==} - dev: false + tinylogic@2.0.0: {} - /tmp-promise@3.0.3: - resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==} + tmp-promise@3.0.3: dependencies: - tmp: 0.2.1 - dev: true + tmp: 0.2.3 - /tmp@0.2.1: - resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} - engines: {node: '>=8.17.0'} - dependencies: - rimraf: 3.0.2 - dev: true + tmp@0.2.3: {} - /tmpl@1.0.5: - resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} - dev: true + tmpl@1.0.5: {} - /to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - dev: true + to-fast-properties@2.0.0: {} - /to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 - /toml-eslint-parser@0.9.3: - resolution: {integrity: sha512-moYoCvkNUAPCxSW9jmHmRElhm4tVJpHL8ItC/+uYD0EpPSFXbck7yREz9tNdJVTSpHVod8+HoipcpbQ0oE6gsw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + toml-eslint-parser@0.9.3: dependencies: eslint-visitor-keys: 3.4.3 - dev: false - /tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - dev: false + tr46@0.0.3: {} - /traverse@0.6.8: - resolution: {integrity: sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==} - engines: {node: '>= 0.4'} + traverse@0.6.9: + dependencies: + gopd: 1.0.1 + typedarray.prototype.slice: 1.0.3 + which-typed-array: 1.1.15 - /treeify@1.1.0: - resolution: {integrity: sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==} - engines: {node: '>=0.6'} - dev: false + tree-dump@1.0.1(tslib@2.6.2): + dependencies: + tslib: 2.6.2 - /trim-newlines@3.0.1: - resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} - engines: {node: '>=8'} - dev: false + treeify@1.1.0: {} - /trough@1.0.5: - resolution: {integrity: sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==} + trim-newlines@3.0.1: {} - /ts-api-utils@1.2.1(typescript@5.3.3): - resolution: {integrity: sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' + triplesec@4.0.3: dependencies: - typescript: 5.3.3 - dev: true + iced-error: 0.0.13 + iced-lock: 1.1.0 + iced-runtime: 1.0.4 + more-entropy: 0.0.7 + progress: 1.1.8 + uglify-js: 3.17.4 - /ts-essentials@7.0.3(typescript@5.3.3): - resolution: {integrity: sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==} - peerDependencies: - typescript: '>=3.7.0' + trough@1.0.5: {} + + ts-api-utils@1.3.0(typescript@5.4.5): dependencies: - typescript: 5.3.3 - dev: true + typescript: 5.4.5 - /ts-jest@29.1.2(@babel/core@7.23.9)(@jest/types@29.6.3)(jest@29.7.0)(typescript@5.3.3): - resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==} - engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@babel/core': '>=7.0.0-beta.0 <8' - '@jest/types': ^29.0.0 - babel-jest: ^29.0.0 - esbuild: '*' - jest: ^29.0.0 - typescript: '>=4.3 <6' - peerDependenciesMeta: - '@babel/core': - optional: true - '@jest/types': - optional: true - babel-jest: - optional: true - esbuild: - optional: true + ts-essentials@10.0.0(typescript@5.4.5): + optionalDependencies: + typescript: 5.4.5 + + ts-jest@29.1.2(@babel/core@7.24.5)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.5))(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5)))(typescript@5.4.5): dependencies: - '@babel/core': 7.23.9 - '@jest/types': 29.6.3 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@18.19.21)(ts-node@10.9.2) + jest: 29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 - semver: 7.5.4 - typescript: 5.3.3 + semver: 7.6.2 + typescript: 5.4.5 yargs-parser: 21.1.1 - dev: true + optionalDependencies: + '@babel/core': 7.24.5 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.24.5) - /ts-node@10.9.2(@swc/core@1.4.2)(@types/node@18.19.21)(typescript@5.3.3): - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true + ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.5): dependencies: '@cspotcode/source-map-support': 0.8.1 - '@swc/core': 1.4.2 - '@tsconfig/node10': 1.0.9 + '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 18.19.21 + '@types/node': 18.19.33 acorn: 8.11.3 acorn-walk: 8.3.2 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.3.3 + typescript: 5.4.5 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - dev: true + optionalDependencies: + '@swc/core': 1.5.7 - /tsconfig-paths@3.15.0: - resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + tsconfig-paths@3.15.0: dependencies: '@types/json5': 0.0.29 json5: 1.0.2 minimist: 1.2.8 strip-bom: 3.0.0 - dev: true - /tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + tslib@1.14.1: {} - /tslib@2.6.2: - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + tslib@2.6.2: {} - /tsutils@3.21.0(typescript@5.3.3): - resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} - 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' + tsutils@3.21.0(typescript@5.4.5): dependencies: tslib: 1.14.1 - typescript: 5.3.3 - dev: true + typescript: 5.4.5 - /tunnel-agent@0.6.0: - resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 - dev: false + optional: true - /tunnel@0.0.6: - resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} - engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} + tunnel@0.0.6: {} - /typanion@3.14.0: - resolution: {integrity: sha512-ZW/lVMRabETuYCd9O9ZvMhAh8GslSqaUjxmK/JLPCh6l73CvLBiuXswj/+7LdnWOgYsQ130FqLzFz5aGT4I3Ug==} - dev: false + tweetnacl@0.13.3: {} - /type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} + tweetnacl@1.0.3: {} + + typanion@3.14.0: {} + + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - dev: true - - /type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - dev: true - /type-fest@0.13.1: - resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} - engines: {node: '>=10'} - dev: false + type-detect@4.0.8: {} - /type-fest@0.18.1: - resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} - engines: {node: '>=10'} - dev: false + type-fest@0.13.1: {} - /type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - dev: true + type-fest@0.18.1: {} - /type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - dev: true + type-fest@0.20.2: {} - /type-fest@0.6.0: - resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} - engines: {node: '>=8'} - dev: false + type-fest@0.21.3: {} - /type-fest@0.8.1: - resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} - engines: {node: '>=8'} + type-fest@0.6.0: {} - /type-fest@1.4.0: - resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} - engines: {node: '>=10'} - dev: true + type-fest@0.8.1: {} - /type-fest@2.19.0: - resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} - engines: {node: '>=12.20'} - dev: true + type-fest@1.4.0: {} - /type-fest@3.13.1: - resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} - engines: {node: '>=14.16'} - dev: true + type-fest@2.19.0: {} - /type-fest@4.10.3: - resolution: {integrity: sha512-JLXyjizi072smKGGcZiAJDCNweT8J+AuRxmPZ1aG7TERg4ijx9REl8CNhbr36RV4qXqL1gO1FF9HL8OkVmmrsA==} - engines: {node: '>=16'} - dev: true + type-fest@4.18.2: {} - /typed-array-buffer@1.0.2: - resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} - engines: {node: '>= 0.4'} + typed-array-buffer@1.0.2: dependencies: call-bind: 1.0.7 es-errors: 1.3.0 is-typed-array: 1.1.13 - dev: true - /typed-array-byte-length@1.0.1: - resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} - engines: {node: '>= 0.4'} + typed-array-byte-length@1.0.1: dependencies: call-bind: 1.0.7 for-each: 0.3.3 gopd: 1.0.1 has-proto: 1.0.3 is-typed-array: 1.1.13 - dev: true - /typed-array-byte-offset@1.0.2: - resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} - engines: {node: '>= 0.4'} + typed-array-byte-offset@1.0.2: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.7 @@ -10445,11 +12972,8 @@ packages: gopd: 1.0.1 has-proto: 1.0.3 is-typed-array: 1.1.13 - dev: true - /typed-array-length@1.0.5: - resolution: {integrity: sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==} - engines: {node: '>= 0.4'} + typed-array-length@1.0.6: dependencies: call-bind: 1.0.7 for-each: 0.3.3 @@ -10457,77 +12981,50 @@ packages: has-proto: 1.0.3 is-typed-array: 1.1.13 possible-typed-array-names: 1.0.0 - dev: true - /typed-rest-client@1.8.11: - resolution: {integrity: sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==} + typed-rest-client@1.8.11: dependencies: - qs: 6.11.2 + qs: 6.12.1 tunnel: 0.0.6 underscore: 1.13.6 - dev: false - /typedarray-to-buffer@3.1.5: - resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} + typedarray-to-buffer@3.1.5: dependencies: is-typedarray: 1.0.0 - /typescript@5.3.3: - resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} - engines: {node: '>=14.17'} - hasBin: true - dev: true + typedarray.prototype.slice@1.0.3: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + typed-array-buffer: 1.0.2 + typed-array-byte-offset: 1.0.2 - /uc.micro@1.0.6: - resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==} - dev: false + typescript@5.4.5: {} - /uc.micro@2.0.0: - resolution: {integrity: sha512-DffL94LsNOccVn4hyfRe5rdKa273swqeA5DJpMOeFmEn1wCDc7nAbbB0gXlgBCL7TNzeTv6G7XVWzan7iJtfig==} - dev: true + uc.micro@2.1.0: {} - /uglify-js@3.17.4: - resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} - engines: {node: '>=0.8.0'} - hasBin: true - requiresBuild: true - optional: true + uglify-js@3.17.4: {} - /unbox-primitive@1.0.2: - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + uint64be@1.0.1: {} + + unbox-primitive@1.0.2: dependencies: call-bind: 1.0.7 has-bigints: 1.0.2 has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 - dev: true - - /underscore@1.13.6: - resolution: {integrity: sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==} - dev: false - /undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + underscore@1.13.6: {} - /undici@5.28.3: - resolution: {integrity: sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==} - engines: {node: '>=14.0'} - dependencies: - '@fastify/busboy': 2.1.0 - dev: true + undici-types@5.26.5: {} - /unicode-emoji-modifier-base@1.0.0: - resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} - engines: {node: '>=4'} - dev: true + unicode-emoji-modifier-base@1.0.0: {} - /unicorn-magic@0.1.0: - resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} - engines: {node: '>=18'} - dev: true + unicorn-magic@0.1.0: {} - /unified@9.2.2: - resolution: {integrity: sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==} + unified@9.2.2: dependencies: '@types/unist': 2.0.10 bail: 1.0.5 @@ -10537,184 +13034,127 @@ packages: trough: 1.0.5 vfile: 4.2.1 - /unique-filename@3.0.0: - resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + unique-filename@3.0.0: dependencies: unique-slug: 4.0.0 - dev: false - /unique-slug@4.0.0: - resolution: {integrity: sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + unique-slug@4.0.0: dependencies: imurmurhash: 0.1.4 - dev: false - /unique-string@3.0.0: - resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==} - engines: {node: '>=12'} + unique-string@3.0.0: dependencies: crypto-random-string: 4.0.0 - dev: true - /unist-util-is@4.1.0: - resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} - dev: false + unist-util-is@4.1.0: {} - /unist-util-stringify-position@2.0.3: - resolution: {integrity: sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==} + unist-util-stringify-position@2.0.3: dependencies: '@types/unist': 2.0.10 - /unist-util-visit-parents@3.1.1: - resolution: {integrity: sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==} + unist-util-visit-parents@3.1.1: dependencies: '@types/unist': 2.0.10 unist-util-is: 4.1.0 - dev: false - /unist-util-visit@2.0.3: - resolution: {integrity: sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==} + unist-util-visit@2.0.3: dependencies: '@types/unist': 2.0.10 unist-util-is: 4.1.0 unist-util-visit-parents: 3.1.1 - dev: false - /universal-user-agent@6.0.1: - resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==} + universal-user-agent@6.0.1: {} - /universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} + universalify@2.0.1: {} - /upath@2.0.1: - resolution: {integrity: sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==} - engines: {node: '>=4'} - dev: false + upath@2.0.1: {} - /update-browserslist-db@1.0.13(browserslist@4.23.0): - resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' + update-browserslist-db@1.0.15(browserslist@4.23.0): dependencies: browserslist: 4.23.0 escalade: 3.1.2 picocolors: 1.0.0 - dev: true - /uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + uri-js@4.4.1: dependencies: punycode: 2.3.1 - dev: true - /url-join@4.0.1: - resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} - dev: false + url-join@4.0.1: {} - /url-join@5.0.0: - resolution: {integrity: sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true + url-join@5.0.0: {} - /util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + util-deprecate@1.0.2: {} - /util@0.12.5: - resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + util@0.12.5: dependencies: inherits: 2.0.4 is-arguments: 1.1.1 is-generator-function: 1.0.10 is-typed-array: 1.1.13 - which-typed-array: 1.1.14 - dev: false + which-typed-array: 1.1.15 - /uuid@8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true + uuid@8.3.2: {} - /v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - dev: true + uuid@9.0.1: {} - /v8-to-istanbul@9.2.0: - resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} - engines: {node: '>=10.12.0'} + v8-compile-cache-lib@3.0.1: {} + + v8-to-istanbul@9.2.0: dependencies: - '@jridgewell/trace-mapping': 0.3.23 + '@jridgewell/trace-mapping': 0.3.25 '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 - dev: true - /validate-npm-package-license@3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 - /validate-npm-package-name@5.0.0: - resolution: {integrity: sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - dependencies: - builtins: 5.0.1 - dev: false + validate-npm-package-name@5.0.1: {} - /vfile-message@2.0.4: - resolution: {integrity: sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==} + vfile-message@2.0.4: dependencies: '@types/unist': 2.0.10 unist-util-stringify-position: 2.0.3 - /vfile@4.2.1: - resolution: {integrity: sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==} + vfile@4.2.1: dependencies: '@types/unist': 2.0.10 is-buffer: 2.0.5 unist-util-stringify-position: 2.0.3 vfile-message: 2.0.4 - /vuln-vects@1.1.0: - resolution: {integrity: sha512-LGDwn9nRz94YoeqOn2TZqQXzyonBc5FJppSgH34S/1U+3bgPONq/vvfiCbCQ4MeBll58xx+kDmhS73ac+EHBBw==} - dev: false + vuln-vects@1.1.0: {} - /walker@1.0.8: - resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + walker@1.0.8: dependencies: makeerror: 1.0.12 - dev: true - /webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - dev: false + webidl-conversions@3.0.1: {} - /whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 - dev: false - /which-boxed-primitive@1.0.2: - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + which-boxed-primitive@1.0.2: dependencies: is-bigint: 1.0.4 is-boolean-object: 1.1.2 is-number-object: 1.0.7 is-string: 1.0.7 is-symbol: 1.0.4 - dev: true - /which-module@2.0.1: - resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - dev: true + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.3 - /which-typed-array@1.1.14: - resolution: {integrity: sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==} - engines: {node: '>= 0.4'} + which-module@2.0.1: {} + + which-typed-array@1.1.15: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.7 @@ -10722,138 +13162,85 @@ packages: gopd: 1.0.1 has-tostringtag: 1.0.2 - /which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true + which@2.0.2: dependencies: isexe: 2.0.0 - /which@4.0.0: - resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} - engines: {node: ^16.13.0 || >=18.0.0} - hasBin: true - requiresBuild: true + which@4.0.0: dependencies: isexe: 3.1.1 - dev: false optional: true - /wordwrap@1.0.0: - resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + word-wrap@1.2.5: {} - /wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} + wordwrap@1.0.0: {} + + wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: true - /wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - /wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} + wrap-ansi@8.1.0: dependencies: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 - /wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - requiresBuild: true + wrappy@1.0.2: {} - /write-file-atomic@3.0.3: - resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} + write-file-atomic@3.0.3: dependencies: imurmurhash: 0.1.4 is-typedarray: 1.0.0 signal-exit: 3.0.7 typedarray-to-buffer: 3.1.5 - /write-file-atomic@4.0.2: - resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + write-file-atomic@4.0.2: dependencies: imurmurhash: 0.1.4 signal-exit: 3.0.7 - dev: true - /write-file-atomic@5.0.1: - resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + write-file-atomic@5.0.1: dependencies: imurmurhash: 0.1.4 signal-exit: 4.1.0 - dev: false - /write-yaml-file@4.2.0: - resolution: {integrity: sha512-LwyucHy0uhWqbrOkh9cBluZBeNVxzHjDaE9mwepZG3n3ZlbM4v3ndrFw51zW/NXYFFqP+QWZ72ihtLWTh05e4Q==} - engines: {node: '>=10.13'} + write-yaml-file@4.2.0: dependencies: js-yaml: 4.1.0 write-file-atomic: 3.0.3 - dev: false - /xmldoc@1.3.0: - resolution: {integrity: sha512-y7IRWW6PvEnYQZNZFMRLNJw+p3pezM4nKYPfr15g4OOW9i8VpeydycFuipE2297OvZnh3jSb2pxOt9QpkZUVng==} + xmldoc@1.3.0: dependencies: sax: 1.3.0 - dev: false - - /xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - /y18n@4.0.3: - resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} - dev: true + xtend@4.0.2: {} - /y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - dev: true + y18n@4.0.3: {} - /yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - dev: true + y18n@5.0.8: {} - /yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yallist@3.1.1: {} - /yaml@2.3.4: - resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==} - engines: {node: '>= 14'} - dev: true + yallist@4.0.0: {} - /yargs-parser@18.1.3: - resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} - engines: {node: '>=6'} + yargs-parser@18.1.3: dependencies: camelcase: 5.3.1 decamelize: 1.2.0 - /yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - dev: false + yargs-parser@20.2.9: {} - /yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - dev: true + yargs-parser@21.1.1: {} - /yargs@15.4.1: - resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} - engines: {node: '>=8'} + yargs@15.4.1: dependencies: cliui: 6.0.0 decamelize: 1.2.0 @@ -10866,11 +13253,8 @@ packages: which-module: 2.0.1 y18n: 4.0.3 yargs-parser: 18.1.3 - dev: true - /yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} + yargs@17.7.2: dependencies: cliui: 8.0.1 escalade: 3.1.2 @@ -10879,33 +13263,16 @@ packages: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 21.1.1 - dev: true - /yauzl@2.10.0: - resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + yauzl@2.10.0: dependencies: buffer-crc32: 0.2.13 fd-slicer: 1.1.0 - dev: false - - /yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - dev: true - /yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} + yn@3.1.1: {} - /zod@3.22.4: - resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} - dev: false + yocto-queue@0.1.0: {} - /zwitch@1.0.5: - resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==} - dev: false + zod@3.23.8: {} - file:tools/eslint: - resolution: {directory: tools/eslint, type: directory} - name: '@renovatebot/eslint-plugin' - dev: true + zwitch@1.0.5: {} diff --git a/readme.md b/readme.md index 69222912a660d3..e4449bf34b0b70 100644 --- a/readme.md +++ b/readme.md @@ -8,7 +8,7 @@ Multi-platform and multi-language. [![License: AGPL-3.0-only](https://img.shields.io/badge/license-%20%09AGPL--3.0--only-blue.svg)](https://raw.githubusercontent.com/renovatebot/renovate/main/license) [![codecov](https://codecov.io/gh/renovatebot/renovate/branch/main/graph/badge.svg)](https://codecov.io/gh/renovatebot/renovate) [![Renovate enabled](https://img.shields.io/badge/renovate-enabled-brightgreen.svg)](https://renovatebot.com/) -[![Build](https://github.com/renovatebot/renovate/actions/workflows/build.yml/badge.svg)](https://github.com/renovatebot/renovate/actions/workflows/build.yml) +[![Build status](https://github.com/renovatebot/renovate/actions/workflows/build.yml/badge.svg)](https://github.com/renovatebot/renovate/actions/workflows/build.yml) ![Docker Pulls](https://img.shields.io/docker/pulls/renovate/renovate?color=turquoise) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/renovatebot/renovate/badge)](https://securityscorecards.dev/viewer/?uri=github.com/renovatebot/renovate) @@ -45,7 +45,33 @@ Renovate works on these platforms: Renovate is widely used in the developer community: -![Renovate Matrix](https://app.renovatebot.com/images/matrix.png) +![Logos of companies and projects that use Renovate](./docs/usage/assets/images/matrix.png) + +
+List of companies and projects that use Renovate + +- Prisma +- Netlify +- Envoy +- Condé Nast +- Microsoft +- Atlassian +- Sourcegraph +- Mozilla +- Deloitte +- Telus +- Yarn +- HashiCorp +- Automattic +- Algolia +- eBay +- Cypress +- Red Hat +- Financial Times +- Uber +- Buildkite + +
## Renovate OSS Insights diff --git a/renovate.json b/renovate.json index 93d077db42114d..93bf4840930b37 100644 --- a/renovate.json +++ b/renovate.json @@ -14,7 +14,7 @@ "versioning": "node" }, { - "matchDepTypes": ["dependencies"], + "matchDepTypes": ["dependencies", "optionalDependencies"], "semanticCommitType": "build" }, { @@ -39,12 +39,6 @@ "matchDepNames": ["ghcr.io/renovatebot/base-image"], "matchUpdateTypes": ["major", "minor"], "semanticCommitType": "feat" - }, - { - "description": "fix versioning for ben-z/gh-action-mutex, eg v1.0-alpha-8", - "matchDepNames": ["ben-z/gh-action-mutex"], - "matchManagers": ["github-actions"], - "versioning": "regex:^v(?\\d+)\\.(?\\d+)(?:\\.(?\\d+))?(?\\-.+)?$" } ], "customManagers": [ diff --git a/test/documentation.spec.ts b/test/documentation.spec.ts index f0f66b1e94f86a..6d1d54ce339926 100644 --- a/test/documentation.spec.ts +++ b/test/documentation.spec.ts @@ -19,84 +19,100 @@ describe('documentation', () => { }); describe('website-documentation', () => { - describe('configuration-options', () => { - const doc = fs.readFileSync( - 'docs/usage/configuration-options.md', - 'utf8', - ); - - const headers = doc - .match(/\n## (.*?)\n/g) - ?.map((match) => match.substring(4, match.length - 1)); - - const expectedOptions = options - .filter((option) => !option.globalOnly) - .filter((option) => !option.parents) - .filter((option) => !option.autogenerated) - .map((option) => option.name) - .sort(); + describe('docs/usage/configuration-options.md', () => { + function getConfigHeaders(file: string): string[] { + const content = fs.readFileSync(`docs/usage/${file}`, 'utf8'); + const matches = content.match(/\n## (.*?)\n/g) ?? []; + return matches.map((match) => match.substring(4, match.length - 1)); + } + + function getRequiredConfigOptions(): string[] { + return options + .filter((option) => !option.globalOnly) + .filter((option) => !option.parents) + .filter((option) => !option.autogenerated) + .map((option) => option.name) + .sort(); + } it('has doc headers sorted alphabetically', () => { - expect(headers).toEqual([...headers!].sort()); + expect(getConfigHeaders('configuration-options.md')).toEqual( + getConfigHeaders('configuration-options.md').sort(), + ); }); it('has headers for every required option', () => { - expect(headers).toEqual(expectedOptions); + expect(getConfigHeaders('configuration-options.md')).toEqual( + getRequiredConfigOptions(), + ); }); - const subHeaders = doc - .match(/\n### (.*?)\n/g) - ?.map((match) => match.substring(5, match.length - 1)); - subHeaders!.sort(); - const expectedSubOptions = options - .filter((option) => option.stage !== 'global') - .filter((option) => !option.globalOnly) - .filter((option) => option.parents) - .map((option) => option.name) - .sort(); - expectedSubOptions.sort(); + function getConfigSubHeaders(file: string): string[] { + const content = fs.readFileSync(`docs/usage/${file}`, 'utf8'); + const matches = content.match(/\n### (.*?)\n/g) ?? []; + return matches + .map((match) => match.substring(5, match.length - 1)) + .sort(); + } + + function getRequiredConfigSubOptions(): string[] { + return options + .filter((option) => option.stage !== 'global') + .filter((option) => !option.globalOnly) + .filter((option) => option.parents) + .map((option) => option.name) + .sort(); + } it('has headers for every required sub-option', () => { - expect(subHeaders).toEqual(expectedSubOptions); + expect(getConfigSubHeaders('configuration-options.md')).toEqual( + getRequiredConfigSubOptions(), + ); }); }); - describe('self-hosted-configuration', () => { - const doc = fs.readFileSync( - 'docs/usage/self-hosted-configuration.md', - 'utf8', - ); + describe('docs/usage/self-hosted-configuration.md', () => { + function getSelfHostedHeaders(file: string): string[] { + const content = fs.readFileSync(`docs/usage/${file}`, 'utf8'); + const matches = content.match(/\n## (.*?)\n/g) ?? []; + return matches.map((match) => match.substring(4, match.length - 1)); + } - const headers = doc - .match(/\n## (.*?)\n/g) - ?.map((match) => match.substring(4, match.length - 1)); - - const expectedOptions = options - .filter((option) => !!option.globalOnly) - .map((option) => option.name) - .sort(); + function getRequiredSelfHostedOptions(): string[] { + return options + .filter((option) => option.globalOnly) + .map((option) => option.name) + .sort(); + } it('has headers sorted alphabetically', () => { - expect(headers).toEqual([...headers!].sort()); + expect(getSelfHostedHeaders('self-hosted-configuration.md')).toEqual( + getSelfHostedHeaders('self-hosted-configuration.md').sort(), + ); }); it('has headers for every required option', () => { - expect(headers).toEqual(expectedOptions); + expect(getSelfHostedHeaders('self-hosted-configuration.md')).toEqual( + getRequiredSelfHostedOptions(), + ); }); }); - describe('self-hosted-experimental', () => { - const doc = fs.readFileSync( - 'docs/usage/self-hosted-experimental.md', - 'utf8', - ); - - const headers = doc - .match(/\n## (.*?)\n/g) - ?.map((match) => match.substring(4, match.length - 1)); + describe('docs/usage/self-hosted-experimental.md', () => { + function getSelfHostedExperimentalConfigHeaders(file: string): string[] { + const content = fs.readFileSync(`docs/usage/${file}`, 'utf8'); + const matches = content.match(/\n## (.*?)\n/g) ?? []; + return matches.map((match) => match.substring(4, match.length - 1)); + } it('has headers sorted alphabetically', () => { - expect(headers).toEqual([...headers!].sort()); + expect( + getSelfHostedExperimentalConfigHeaders('self-hosted-experimental.md'), + ).toEqual( + getSelfHostedExperimentalConfigHeaders( + 'self-hosted-experimental.md', + ).sort(), + ); }); }); }); diff --git a/test/fixtures.ts b/test/fixtures.ts index f629830edf9b81..4b33dea72d1b11 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -26,6 +26,16 @@ export class Fixtures { ); } + /** + * Returns path to fixture file in __fixtures__ folder + * @param name name of the fixture file + * @param [fixturesRoot] - Where to find the fixtures, uses the current test folder by default + * @return path to the fixture + */ + static getPath(name: string, fixturesRoot = '.'): string { + return upath.resolve(Fixtures.getPathToFixtures(fixturesRoot), name); + } + /** * Returns content from fixture file from __fixtures__ folder as `Buffer` * @param name name of the fixture file diff --git a/test/s3.ts b/test/s3.ts new file mode 100644 index 00000000000000..85f94122e7cffc --- /dev/null +++ b/test/s3.ts @@ -0,0 +1,4 @@ +import { jest } from '@jest/globals'; +import * as _s3 from '../lib/util/s3'; + +export const s3 = jest.mocked(_s3); diff --git a/tools/check-fenced-code.mjs b/tools/check-fenced-code.mjs index ff9f841f4ca28b..84e03cd0c86a3b 100644 --- a/tools/check-fenced-code.mjs +++ b/tools/check-fenced-code.mjs @@ -15,7 +15,7 @@ markdown.enable(['fence']); /** * * @param {string} file - * @param {import('markdown-it/lib/token')} token + * @param {import('markdown-it').Token} token */ function checkValidJson(file, token) { const start = token.map ? token.map[0] + 1 : 0; diff --git a/tools/docker/Dockerfile b/tools/docker/Dockerfile index c85922ab9676bc..582486151e1ab1 100644 --- a/tools/docker/Dockerfile +++ b/tools/docker/Dockerfile @@ -3,12 +3,12 @@ ARG BASE_IMAGE_TYPE=slim # -------------------------------------- # slim image # -------------------------------------- -FROM ghcr.io/renovatebot/base-image:1.26.0@sha256:0a7ac38631a6ac43cfdf338a3d3ea6c9fe5ed2e531cd30754684a08875ff8359 AS slim-base +FROM ghcr.io/renovatebot/base-image:2.12.7@sha256:12a8af69f113f56aae18f04b8798545976f2602097e834b8e2e2985f320bf68d AS slim-base # -------------------------------------- # full image # -------------------------------------- -FROM ghcr.io/renovatebot/base-image:1.26.0-full@sha256:89af456ca951561cb7abcd014b57879e6312702e7492d2e7f0390b262818fcd4 AS full-base +FROM ghcr.io/renovatebot/base-image:2.12.7-full@sha256:504c415723b2dbef28fd12aa90a4261e3590a117e3bb2ee618b1c9b4157fe253 AS full-base # -------------------------------------- # build image @@ -18,19 +18,17 @@ FROM slim-base as build WORKDIR /usr/local/renovate ENV CI=1 npm_config_modules_cache_max_age=0 -ENV RE2_DOWNLOAD_MIRROR=https://github.com/containerbase/node-re2-prebuild/releases/download RE2_DOWNLOAD_SKIP_PATH=1 COPY pnpm-lock.yaml ./ # only fetch deps from lockfile https://pnpm.io/cli/fetch -RUN pnpm fetch --prod +RUN corepack pnpm fetch --prod COPY . ./ # install RUN set -ex; \ - pnpm install --prod --offline --ignore-scripts; \ - npm explore re2 -- npm run install; \ + corepack pnpm install --prod --offline --ignore-scripts; \ true # test diff --git a/tools/docs/config.ts b/tools/docs/config.ts index ac7bfe1bb4f4fd..cac650c7101e8e 100644 --- a/tools/docs/config.ts +++ b/tools/docs/config.ts @@ -1,3 +1,4 @@ +import is from '@sindresorhus/is'; import stringify from 'json-stringify-pretty-compact'; import { getOptions } from '../../lib/config/options'; import { allManagersList } from '../../lib/modules/manager'; @@ -92,6 +93,8 @@ function genTable(obj: [string, string][], type: string, def: any): string { 'experimentalDescription', 'experimentalIssues', 'advancedUse', + 'deprecationMsg', + 'patternMatch', ]; obj.forEach(([key, val]) => { const el = [key, val]; @@ -179,6 +182,17 @@ function genExperimentalMsg(el: Record): string { return warning + '\n'; } +function genDeprecationMsg(el: Record): string { + let warning = + '\n\n!!! warning "This feature has been deprecated"\n'; + + if (el.deprecationMsg) { + warning += indent`${2}${el.deprecationMsg}`; + } + + return warning + '\n'; +} + function indexMarkdown(lines: string[]): Record { const indexed: Record = {}; @@ -241,6 +255,10 @@ export async function generateConfig(dist: string, bot = false): Promise { if (el.experimental) { configOptionsRaw[footerIndex] += genExperimentalMsg(el); } + + if (is.nonEmptyString(el.deprecationMsg)) { + configOptionsRaw[footerIndex] += genDeprecationMsg(el); + } }); await updateFile(`${dist}/${configFile}`, configOptionsRaw.join('\n')); diff --git a/tools/docs/github-query-items.ts b/tools/docs/github-query-items.ts index 5aeb4e282b3d21..13527c800eca6b 100644 --- a/tools/docs/github-query-items.ts +++ b/tools/docs/github-query-items.ts @@ -8,7 +8,7 @@ const gitHubApiUrl = 'https://api.github.com/search/issues?'; const githubApi = new GithubHttp(); if (process.env.GITHUB_TOKEN) { - logger.debug('Using GITHUB_TOKEN from env'); + logger.info('Using GITHUB_TOKEN from env'); hostRules.add({ matchHost: 'api.github.com', token: process.env.GITHUB_TOKEN, @@ -36,6 +36,7 @@ export interface RenovateOpenItems { managers: OpenItems; platforms: OpenItems; datasources: OpenItems; + versionings: OpenItems; } export type OpenItems = Record; @@ -46,6 +47,18 @@ export interface Items { } export async function getOpenGitHubItems(): Promise { + const result: RenovateOpenItems = { + managers: {}, + platforms: {}, + datasources: {}, + versionings: {}, + }; + + if (process.env.SKIP_GITHUB_ISSUES) { + logger.warn('Skipping GitHub issues'); + return result; + } + const q = `repo:renovatebot/renovate type:issue is:open`; const per_page = 100; try { @@ -59,19 +72,18 @@ export async function getOpenGitHubItems(): Promise { ); const rawItems = res.body?.items ?? []; - const renovateOpenItems: RenovateOpenItems = { - managers: extractIssues(rawItems, 'manager:'), - platforms: extractIssues(rawItems, 'platform:'), - datasources: extractIssues(rawItems, 'datasource:'), - }; + result.managers = extractIssues(rawItems, 'manager:'); + result.platforms = extractIssues(rawItems, 'platform:'); + result.datasources = extractIssues(rawItems, 'datasource:'); + result.versionings = extractIssues(rawItems, 'versioning:'); - return renovateOpenItems; + return result; } catch (err) { logger.error({ err }, 'Error getting query results'); if (process.env.CI) { throw err; } - return { managers: {}, platforms: {}, datasources: {} }; + return result; } } diff --git a/tools/docs/index.ts b/tools/docs/index.ts index 577f0d6e9ec0c7..737138b1082c2a 100644 --- a/tools/docs/index.ts +++ b/tools/docs/index.ts @@ -32,7 +32,7 @@ export async function generateDocs(): Promise { // versionings logger.info('* versionings'); - await generateVersioning(dist); + await generateVersioning(dist, openItems.versionings); // datasources logger.info('* datasources'); diff --git a/tools/docs/test/index.test.mjs b/tools/docs/test/index.test.mjs new file mode 100644 index 00000000000000..ed52a9299c617a --- /dev/null +++ b/tools/docs/test/index.test.mjs @@ -0,0 +1,72 @@ +import assert from 'node:assert'; +import path from 'node:path'; +import { describe, it } from 'node:test'; +import fs from 'fs-extra'; +import { glob } from 'glob'; +import remark from 'remark'; +import github from 'remark-github'; + +const root = path.resolve('tmp/docs'); + +/** + * @param {any} node + * @param {Set} files + * @param {string} file + */ +function checkNode(node, files, file) { + if (node.type === 'link') { + /** @type {import('mdast').Link} */ + const link = node; + assert.ok( + !link.url.startsWith('/'), + `Link should be external or relative: ${link.url}`, + ); + + if (link.url.startsWith('.') && !/^https?:\/\//.test(link.url)) { + // absolute path + const absPath = path.resolve( + 'tmp/docs', + path.dirname(file), + link.url.replace(/#.*/, ''), + ); + // relative path + const relPath = absPath.substring(root.length + 1); + + assert.ok( + files.has(relPath), + `File not found: ${link.url} in ${file} -> ${relPath}`, + ); + } else { + assert.ok( + !link.url.startsWith('https://docs.renovatebot.com/'), + `Docs links should be relative: ${link.url}`, + ); + } + } else if ('children' in node) { + for (const child of node.children) { + checkNode(child, files, file); + } + } +} + +describe('index', async () => { + await describe('validate links', async () => { + const todo = await glob('**/*.md', { cwd: 'tmp/docs' }); + const files = new Set(todo); + + let c = 0; + + for (const file of todo) { + c++; + + await it(`${file}`, async () => { + const node = remark() + .use(github) + .parse(await fs.readFile(`tmp/docs/${file}`, 'utf8')); + checkNode(node, files, file); + }); + } + + assert.ok(c > 0, 'Should find at least one file'); + }); +}); diff --git a/tools/docs/versioning.ts b/tools/docs/versioning.ts index 4eb85beb5bce0d..eb379f357b0e39 100644 --- a/tools/docs/versioning.ts +++ b/tools/docs/versioning.ts @@ -1,6 +1,16 @@ +import { codeBlock } from 'common-tags'; import { getVersioningList } from '../../lib/modules/versioning'; import { readFile, updateFile } from '../utils'; -import { formatDescription, formatUrls, replaceContent } from './utils'; +import { + type OpenItems, + generateFeatureAndBugMarkdown, +} from './github-query-items'; +import { + formatDescription, + formatUrls, + getModuleLink, + replaceContent, +} from './utils'; type Versioning = { id: string; @@ -10,35 +20,51 @@ type Versioning = { supportedRangeStrategies?: string[]; }; -export async function generateVersioning(dist: string): Promise { +export async function generateVersioning( + dist: string, + versioningIssuesMap: OpenItems, +): Promise { const versioningList = getVersioningList(); - let versioningContent = - '\nSupported values for `versioning` are: ' + - versioningList.map((v) => `\`${v}\``).join(', ') + - '.\n\n'; + let versioningContent = '\nSupported values for `versioning` are:\n\n'; for (const versioning of versioningList) { const definition = (await import( `../../lib/modules/versioning/${versioning}` )) as Versioning; const { id, displayName, urls, supportsRanges, supportedRangeStrategies } = definition; - versioningContent += `\n### ${displayName} Versioning\n\n`; - versioningContent += `**Identifier**: \`${id}\`\n\n`; - versioningContent += formatUrls(urls); - versioningContent += `**Ranges/Constraints:**\n\n`; + versioningContent += `* ${getModuleLink( + versioning, + `\`${versioning}\``, + )}\n`; + let md = codeBlock` + --- + title: ${displayName} + edit_url: https://github.com/renovatebot/renovate/edit/main/lib/modules/versioning/${versioning}/readme.md + --- + + # ${displayName} Versioning + `; + md += '\n\n'; + md += `**Identifier**: \`${id}\`\n\n`; + md += formatUrls(urls); + md += `**Ranges/Constraints:**\n\n`; if (supportsRanges) { - versioningContent += `✅ Ranges are supported.\n\nValid \`rangeStrategy\` values are: ${( + md += `✅ Ranges are supported.\n\nValid \`rangeStrategy\` values are: ${( supportedRangeStrategies ?? [] ) .map((strategy: string) => `\`${strategy}\``) .join(', ')}\n\n`; } else { - versioningContent += `❌ No range support.\n\n`; + md += `❌ No range support.\n\n`; } - versioningContent += await formatDescription('versioning', versioning); - versioningContent += `\n----\n\n`; + md += await formatDescription('versioning', versioning); + md += `\n----\n\n`; + md += generateFeatureAndBugMarkdown(versioningIssuesMap, versioning); + + await updateFile(`${dist}/modules/versioning/${versioning}/index.md`, md); } - let indexContent = await readFile(`docs/usage/modules/versioning.md`); + + let indexContent = await readFile(`docs/usage/modules/versioning/index.md`); indexContent = replaceContent(indexContent, versioningContent); - await updateFile(`${dist}/modules/versioning.md`, indexContent); + await updateFile(`${dist}/modules/versioning/index.md`, indexContent); } diff --git a/tools/jest.mjs b/tools/jest.mjs deleted file mode 100644 index 914cf82b36b106..00000000000000 --- a/tools/jest.mjs +++ /dev/null @@ -1,32 +0,0 @@ -import { spawnSync } from 'node:child_process'; -import { argv, env, version } from 'node:process'; -import semver from 'semver'; - -// needed for tests -env.GIT_ALLOW_PROTOCOL = 'file'; -// reduce logging -env.LOG_LEVEL = 'fatal'; - -const args = ['--experimental-vm-modules']; - -/* - * openpgp encryption is broken because it needs PKCS#1 v1.5 - * - #27375 - * - https://nodejs.org/en/blog/vulnerability/february-2024-security-releases#nodejs-is-vulnerable-to-the-marvin-attack-timing-variant-of-the-bleichenbacher-attack-against-pkcs1-v15-padding-cve-2023-46809---medium - * - * Sadly there is no way to suppress `SECURITY WARNING: Reverting CVE-2023-46809: Marvin attack on PKCS#1 padding` warining - */ -if (semver.satisfies(version, '^18.19.1 || ^20.11.1 || >=21.6.2')) { - args.push('--security-revert=CVE-2023-46809'); -} - -args.push('node_modules/jest/bin/jest.js', '--logHeapUsage'); - -// add other args after `node tools/jest.mjs` -args.push(...argv.slice(2)); - -const res = spawnSync('node', args, { stdio: 'inherit', env }); - -if (res.status !== null && res.status !== 0) { - process.exit(res.status); -}