diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 91db290e6bdfc1..dcf83c5037b3a0 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1 +1 @@ -FROM ghcr.io/containerbase/devcontainer:13.5.8 +FROM ghcr.io/containerbase/devcontainer:13.7.2 diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml index 1ac3793460792c..3b7dc789304c0d 100644 --- a/.github/actions/setup-node/action.yml +++ b/.github/actions/setup-node/action.yml @@ -53,7 +53,7 @@ runs: standalone: true - name: Setup Node - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 with: node-version: ${{ inputs.node-version }} diff --git a/.github/label-actions.yml b/.github/label-actions.yml index 37da913626989b..d19aae3bbf4636 100644 --- a/.github/label-actions.yml +++ b/.github/label-actions.yml @@ -263,6 +263,21 @@ Good luck, + The Renovate team + +'auto:no-merge-from-main': + comment: > + Hi there, + + + Please don't merge from `main` into your PR's branch unless you have a merge conflict or have a specific reason to do so. + Doing so consumes CI systems unnecessarily, and also resets CI approvals if this is your first PR to the repo. + This repo uses GitHub's Merge Queue, which will automatically test your PR against `main` before merging, so it's no longer necessary to always keep branches up-to-date. + + + Thanks, + + The Renovate team 'auto:discussion-first': diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b796acb14c535c..13ee0ce1e9472e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,7 +31,7 @@ concurrency: env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} NODE_VERSION: 22 - PDM_VERSION: 2.22.1 # renovate: datasource=pypi depName=pdm + PDM_VERSION: 2.22.3 # renovate: datasource=pypi depName=pdm DRY_RUN: true TEST_LEGACY_DECRYPTION: true SPARSE_CHECKOUT: |- @@ -304,7 +304,7 @@ jobs: os: ${{ runner.os }} - name: Lint markdown - uses: DavidAnson/markdownlint-cli2-action@eb5ca3ab411449c66620fe7f1b3c9e10547144b0 # v18.0.0 + uses: DavidAnson/markdownlint-cli2-action@05f32210e84442804257b2a6f20b273450ec8265 # v19.1.0 - name: Lint fenced code blocks run: pnpm doc-fence-check @@ -345,6 +345,9 @@ jobs: - name: Test schema run: pnpm test-schema + - name: Lint other + run: pnpm lint-other + test: needs: [setup, prefetch] @@ -411,7 +414,7 @@ jobs: - name: Save coverage artifacts if: (success() || failure()) && github.event.pull_request.draft != true && matrix.coverage - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: ${{ matrix.upload-artifact-name }} path: | @@ -438,7 +441,7 @@ jobs: merge-multiple: true - name: Codecov - uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2 + uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1 with: token: ${{ secrets.CODECOV_TOKEN }} directory: coverage/lcov @@ -559,15 +562,18 @@ jobs: run: pnpm build - name: Build docker - run: pnpm build:docker build --tries=3 + run: pnpm build:docker build --tries=3 --args '--load' env: LOG_LEVEL: debug + - name: Test docker + run: docker run -e LOG_LEVEL=debug --rm renovate/renovate --version + - name: Pack run: pnpm test-e2e:pack - name: Upload - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: renovate-package path: renovate-0.0.0-semantic-release.tgz @@ -611,7 +617,7 @@ jobs: run: pnpm test:docs - name: Upload - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: docs path: tmp/docs/ @@ -638,7 +644,7 @@ jobs: standalone: true - name: Setup Node.js - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 with: node-version: ${{ env.NODE_VERSION }} @@ -684,7 +690,7 @@ jobs: show-progress: false - name: docker-config - uses: containerbase/internal-tools@c8f78cbc830d1883e695d06e3028136656e70f5b # v3.5.17 + uses: containerbase/internal-tools@1d0c5b3aabe4a10a264d309f65ecbbd83d0e672e # v3.5.21 with: command: docker-config diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index fbcd8f0dd563ad..88bba1a8f4ccdc 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -41,7 +41,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/init@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 with: languages: javascript @@ -51,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@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/autobuild@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 # ℹī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -65,4 +65,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/analyze@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index b3cfe0671e90ed..332692d261dec3 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -43,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@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: SARIF file path: results.sarif @@ -51,6 +51,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/upload-sarif@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 with: sarif_file: results.sarif diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 4bb06dc0427ed2..eb93e6460e2d65 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -31,7 +31,7 @@ jobs: format: 'sarif' output: 'trivy-results.sarif' - - uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + - uses: github/codeql-action/upload-sarif@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 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 218d7f1c1a683f..b4199b3c3d470a 100644 --- a/.github/workflows/update-data.yml +++ b/.github/workflows/update-data.yml @@ -27,7 +27,7 @@ jobs: standalone: true - name: Set up Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 with: node-version: ${{ env.NODE_VERSION }} cache: pnpm diff --git a/.nvmrc b/.nvmrc index 7af24b7ddbde0c..d5b283a3acacfd 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.11.0 +22.13.1 diff --git a/docs/development/local-development.md b/docs/development/local-development.md index 1dbc13ff1dd500..43e0b0ae7f513d 100644 --- a/docs/development/local-development.md +++ b/docs/development/local-development.md @@ -177,6 +177,7 @@ If you're only working on the documentation files, you can use the `pnpm doc-fix ## Documentation We use [MkDocs](https://www.mkdocs.org) to generate the documentation. +To install the required dependency, use `pdm install`. You can run `pnpm build:docs` to generate the docs. Then use `pnpm mkdocs serve` to preview the documentation locally. The docs will update automatically when you run `pnpm build:docs` again, no need to stop the `pnpm mkdocs serve` command. diff --git a/docs/usage/bazel.md b/docs/usage/bazel.md index 3ef033f1685ecd..14474a8e9e5586 100644 --- a/docs/usage/bazel.md +++ b/docs/usage/bazel.md @@ -80,6 +80,7 @@ If Renovate finds a newer version, it updates `0.15.0` to match that version. #### `git_override` If Renovate finds a [`git_override`](https://bazel.build/rules/lib/globals/module#git_override), it ignores the related `bazel_dep` entry and instead evaluates the `commit` value at the specified `remote`. +When using `git_override`, the `version` parameter on the `bazel_dep` is optional. ```python bazel_dep(name = "cgrindel_bazel_starlib", version = "0.15.0") @@ -89,6 +90,13 @@ git_override( commit = "fb47f0e9f7c376a7700fc9fe3319231ae57880df", remote = "https://github.com/cgrindel/bazel-starlib.git", ) + +bazel_dep(name = "rules_foo") +git_override( + module_name = "rules_foo", + remote = "https://github.com/foo/rules_foo.git", + commit = "8a1e9abe415eda7cd7f2a744fdac7499ce42cdca", +) ``` If the primary branch has a newer commit than in the list, Renovate updates the `commit` value. @@ -101,6 +109,7 @@ Renovate only evaluates _two_ attributes from this declaration: `version` and `r If a `version` is specified, it overrides the version in the `bazel_dep`. In the following example, Renovate notices that the version is pinned to `1.2.3`. This results in `rules_foo` being ignored for update evaluation. +When using `single_version_override`, the `version` parameter on the `bazel_dep` is optional. ```python bazel_dep(name = "rules_foo", version = "1.2.4") @@ -109,6 +118,13 @@ single_version_override( module_name = "rules_foo", version = "1.2.3", ) + +bazel_dep(name = "rules_bar") + +single_version_override( + module_name = "rules_bar", + version = "1.2.3", +) ``` If a `registry` is specified, Renovate uses the specified registry URL to check for a new version. @@ -128,6 +144,7 @@ single_version_override( If Renovate finds an [`archive_override`](https://bazel.build/rules/lib/globals/module#archive_override) or a [`local_path_override`](https://bazel.build/rules/lib/globals/module#local_path_override), it ignores the related `bazel_dep`. Because these declarations lack versionable attributes, Renovate does not update them. +When using `archive_override` and `local_path_override`, the `version` parameter on the `bazel_dep` is optional. ```python bazel_dep(name = "rules_foo", version = "1.2.3") @@ -138,6 +155,15 @@ archive_override( "https://example.com/archive.tar.gz", ], ) + +bazel_dep(name = "rules_bar") + +archive_override( + module_name = "rules_bar", + urls = [ + "https://example.com/archive.tar.gz", + ], +) ``` #### `multiple_version_override` diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index e76873af8a49d0..63742895d4251f 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -706,22 +706,35 @@ You can define custom managers to handle: - Proprietary file formats or conventions - Popular file formats not yet supported as a manager by Renovate -Currently we only have one custom manager. -The `regex` manager which is based on using Regular Expression named capture groups. +Renovate has two custom managers: -You must have a named capture group matching (e.g. `(?.*)`) _or_ configure its corresponding template (e.g. `depNameTemplate`) for these fields: +| Custom manager | Matching engine | +| -------------- | ---------------------------------------------- | +| `regex` | Regular Expression, with named capture groups. | +| `jsonata` | JSONata query. | + +To use a custom manager, you need give some information: + +1. `fileMatch`: name/pattern of the file to extract deps from +1. `matchStrings`: `regex` patterns or `jsonata` queries used to process the file + +The `matchStrings` must capture/extract the following three fields: - `datasource` - `depName` and / or `packageName` - `currentValue` -Use named capture group matching _or_ set a corresponding template. -We recommend you use only _one_ of these methods, or you'll get confused. +Alteratively, you could also use corresponding templates (e.g. `depNameTemplate`) for these fields. +But, we recommend you use only _one_ of these methods, or you'll get confused. + +Also, we recommend you explicitly set which `versioning` Renovate should use. -We recommend that you also tell Renovate what `versioning` to use. -If the `versioning` field is missing, then Renovate defaults to using `semver` versioning. +Renovate defaults to `semver-coerced` versioning if _both_ condition are met: -For more details and examples about it, see our [documentation for the `regex` manager](modules/manager/regex/index.md). +- The `versioning` field is missing in the custom manager config +- The Renovate datasource does _not_ set its own default versioning + +For more details and examples regarding each custom manager, see our documentation for the [`regex` manager](modules/manager/regex/index.md) and the [`JSONata` manager](modules/manager/jsonata/index.md). For template fields, use the triple brace `{{{ }}}` notation to avoid Handlebars escaping any special characters. @@ -763,6 +776,10 @@ This will lead to following update where `1.21-alpine` is the newest version of image: my.new.registry/aRepository/andImage:1.21-alpine ``` + +!!! note + Can only be used with the custom regex manager. + ### currentValueTemplate If the `currentValue` for a dependency is not captured with a named group then it can be defined in config using this field. @@ -770,6 +787,8 @@ It will be compiled using Handlebars and the regex `groups` result. ### customType +It specifies which custom manager to use. There are two available options: `regex` and `jsonata`. + Example: ```json @@ -786,9 +805,24 @@ Example: } ``` +```json title="Parsing a JSON file with a custom manager" +{ + "customManagers": [ + { + "customType": "jsonata", + "fileFormat": "json", + "fileMatch": ["file.json"], + "matchStrings": [ + "packages.{ \"depName\": package, \"currentValue\": version }" + ] + } + ] +} +``` + ### datasourceTemplate -If the `datasource` for a dependency is not captured with a named group then it can be defined in config using this field. +If the `datasource` for a dependency is not captured with a named group, then it can be defined in config using this field. It will be compiled using Handlebars and the regex `groups` result. ### depNameTemplate @@ -803,16 +837,61 @@ It will be compiled using Handlebars and the regex `groups` result. ### extractVersionTemplate -If `extractVersion` cannot be captured with a named capture group in `matchString` then it can be defined manually using this field. +If `extractVersion` cannot be captured with a named capture group in `matchString`, then it can be defined manually using this field. It will be compiled using Handlebars and the regex `groups` result. +### fileFormat + + +!!! note + Can only be used with the custom jsonata manager. + +It specifies the syntax of the package file that's managed by the custom `jsonata` manager. +This setting helps the system correctly parse and interpret the configuration file's contents. + +Only the `json` and `yaml` format is supported. +`yaml` files are parsed as multi document YAML files. + +```json title="Parsing a JSON file with a custom manager" +{ + "customManagers": [ + { + "customType": "jsonata", + "fileFormat": "json", + "fileMatch": [".renovaterc"], + "matchStrings": [ + "packages.{ \"depName\": package, \"currentValue\": version }" + ] + } + ] +} +``` + +```json title="Parsing a YAML file with a custom manager" +{ + "customManagers": [ + { + "customType": "jsonata", + "fileFormat": "yaml", + "fileMatch": ["file.yml"], + "matchStrings": [ + "packages.{ \"depName\": package, \"currentValue\": version }" + ] + } + ] +} +``` + ### matchStrings -Each `matchStrings` must be a valid regular expression, optionally with named capture groups. +Each `matchStrings` must be one of the following: + +1. A valid regular expression, which may optionally include named capture groups (if using `customType=regex`) +2. Or, a valid, escaped [JSONata](https://docs.jsonata.org/overview.html) query (if using `customType=json`) Example: -```json +```json title="matchStrings with a valid regular expression" { "matchStrings": [ "ENV .*?_VERSION=(?.*) # (?.*?)/(?.*?)\\s" @@ -820,6 +899,14 @@ Example: } ``` +```json title="matchStrings with a valid JSONata query" +{ + "matchStrings": [ + "packages.{ \"depName\": package, \"currentValue\": version }" + ] +} +``` + ### matchStringsStrategy `matchStringsStrategy` controls behavior when multiple `matchStrings` values are provided. @@ -829,6 +916,10 @@ Three options are available: - `recursive` - `combination` + +!!! note + `matchStringsStrategy` can only be used in a custom regex manager config! + #### any Each provided `matchString` will be matched individually to the content of the `packageFile`. @@ -3173,7 +3264,6 @@ Managers which do not support replacement: - `gomod` - `gradle` - `homebrew` -- `maven` - `regex` - `sbt` @@ -3374,7 +3464,7 @@ Table with options: Post-upgrade tasks are commands that are executed by Renovate after a dependency has been updated but before the commit is created. The intention is to run any other command line tools that would modify existing files or generate new files when a dependency changes. -Each command must match at least one of the patterns defined in `allowedPostUpgradeCommands` (a global-only configuration option) in order to be executed. +Each command must match at least one of the patterns defined in `allowedCommands` (a global-only configuration option) in order to be executed. If the list of allowed tasks is empty then no tasks will be executed. e.g. @@ -3395,7 +3485,7 @@ The `postUpgradeTasks` configuration consists of three fields: A list of commands that are executed after Renovate has updated a dependency but before the commit is made. -You can use variable templating in your commands as long as [`allowPostUpgradeCommandTemplating`](./self-hosted-configuration.md#allowpostupgradecommandtemplating) is enabled. +You can use variable templating in your commands as long as [`allowCommandTemplating`](./self-hosted-configuration.md#allowcommandtemplating) is enabled. !!! note diff --git a/docs/usage/docker.md b/docs/usage/docker.md index 03858074ebb1f8..d43678fde413c5 100644 --- a/docs/usage/docker.md +++ b/docs/usage/docker.md @@ -307,7 +307,7 @@ Renovate will get the credentials with the [`google-auth-library`](https://www.n service_account: ${{ env.SERVICE_ACCOUNT }} - name: renovate - uses: renovatebot/github-action@v41.0.8 + uses: renovatebot/github-action@v41.0.11 env: RENOVATE_HOST_RULES: | [ @@ -478,7 +478,7 @@ Make sure to install the Google Cloud SDK into the custom image, as you need the For example: ```Dockerfile -FROM renovate/renovate:39.91.0 +FROM renovate/renovate:39.134.0 # 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/opentelemetry.md b/docs/usage/examples/opentelemetry.md index ebbc5bea1a353b..6225f08569db35 100644 --- a/docs/usage/examples/opentelemetry.md +++ b/docs/usage/examples/opentelemetry.md @@ -36,7 +36,7 @@ services: otel-collector: # Using the Contrib version to access the spanmetrics connector. # If you don't need the spanmetrics connector, you can use the standard version - image: otel/opentelemetry-collector-contrib:0.116.1 + image: otel/opentelemetry-collector-contrib:0.118.0 volumes: - ./otel-collector-config.yml:/etc/otelcol-contrib/config.yaml ports: diff --git a/docs/usage/examples/self-hosting.md b/docs/usage/examples/self-hosting.md index 2426c9e41122ae..a333b13848e609 100644 --- a/docs/usage/examples/self-hosting.md +++ b/docs/usage/examples/self-hosting.md @@ -25,8 +25,8 @@ It builds `latest` based on the `main` branch and all SemVer tags are published ```sh title="Example of valid tags" docker run --rm renovate/renovate docker run --rm renovate/renovate:39 -docker run --rm renovate/renovate:39.91 -docker run --rm renovate/renovate:39.91.0 +docker run --rm renovate/renovate:39.134 +docker run --rm renovate/renovate:39.134.0 ``` @@ -62,7 +62,7 @@ spec: - name: renovate # Update this to the latest available and then enable Renovate on # the manifest - image: renovate/renovate:39.91.0 + image: renovate/renovate:39.134.0 args: - user/repo # Environment Variables @@ -121,7 +121,7 @@ spec: template: spec: containers: - - image: renovate/renovate:39.91.0 + - image: renovate/renovate:39.134.0 name: renovate-bot env: # For illustration purposes, please use secrets. - name: RENOVATE_PLATFORM @@ -367,7 +367,7 @@ spec: containers: - name: renovate # Update this to the latest available and then enable Renovate on the manifest - image: renovate/renovate:39.91.0 + image: renovate/renovate:39.134.0 volumeMounts: - name: ssh-key-volume readOnly: true diff --git a/docs/usage/mend-hosted/.pages b/docs/usage/mend-hosted/.pages index 6b4053c88bf23d..4dda5349d7d4e7 100644 --- a/docs/usage/mend-hosted/.pages +++ b/docs/usage/mend-hosted/.pages @@ -1,6 +1,6 @@ title: Mend-hosted Apps nav: - - 'Renovate Plans': 'renovate-plans.md' + - 'Overview': 'overview.md' - 'Configuration': 'hosted-apps-config.md' - 'Credentials': 'credentials.md' - 'Migrating Secrets': 'migrating-secrets.md' diff --git a/docs/usage/mend-hosted/hosted-apps-config.md b/docs/usage/mend-hosted/hosted-apps-config.md index 787ec273bf84f1..202d4eea4cbae0 100644 --- a/docs/usage/mend-hosted/hosted-apps-config.md +++ b/docs/usage/mend-hosted/hosted-apps-config.md @@ -1,7 +1,5 @@ # Mend-hosted Apps Configuration -The Mend-hosted apps ([Renovate App on GitHub](https://github.com/apps/renovate) and [Mend App on Bitbucket](https://marketplace.atlassian.com/apps/1232072/mend)) are popular ways to use Renovate on the cloud. - This page: - covers all non-default Renovate behavior of these Mend-hosted apps @@ -74,12 +72,12 @@ This change causes Renovate to create an Onboarding PR, even if Renovate does no ## Fork Processing -If an Organization installs Renovate with the "All repositories" option, then `forkProcessing` will remain set to its default value `false`. +If an Organization installs Renovate with the "All repositories" option, then `forkProcessing` will remain set to its default value `disabled`. This means forked repositories are _not_ onboarded, Renovate ignores them. -To change this behavior, push a `renovate.json` file to the repository with `"forkProcessing": true`. +To change this behavior, push a `renovate.json` file to the repository with `"forkProcessing": "enabled"`. If an Organization installs Renovate with "Selected repositories", we assume the organization wants to onboard _all_ of the selected repositories, even forked repositories. -Therefore we set `forkProcessing` to `true`. +Therefore we set `forkProcessing` to "enabled". ## Inherited config diff --git a/docs/usage/mend-hosted/overview.md b/docs/usage/mend-hosted/overview.md new file mode 100644 index 00000000000000..576072aa088234 --- /dev/null +++ b/docs/usage/mend-hosted/overview.md @@ -0,0 +1,59 @@ +# Mend Renovate Cloud-hosted (Community and Enterprise) + +Mend provides cloud hosting services for running Renovate in free and paid versions: + +- Mend Renovate Community Cloud (Free) +- Mend Renovate Enterprise Cloud + +They are available for Git repositories hosted on the following cloud platforms: + +- GitHub +- Bitbucket Cloud +- Azure DevOps + +Mend Renovate cloud will regularly schedule Renovate jobs against all installed repositories. +It also listens to webhooks and enqueues a Renovate job when relevant changes occur in a repo, or when actions are triggered from the Renovate PRs or Dashboard issue. +There is a web UI with functionality to view and interact with installed repositories, their jobs and job logs. + +## Getting started + +To get started using Mend Renovate Cloud versions, access the Developer Portal at [https://developer.mend.io/](https://developer.mend.io/). + +Developers can log in using the OAuth credentials from their cloud-based Git repository. + +![Developer Portal sign-in screen](../assets/images/portal-sign-in.png) + +Features of the Developer Portal include: + +- Ability to install, uninstall and view installed repositories +- Trigger Renovate jobs to run on demand +- View logs for all Renovate jobs +- Configure settings that apply at the Org-level or Repo-level + +## Resources and Scheduling + +The resources, scheduling and concurrency of Renovate jobs is determined by the version of Mend Renovate used by the Org. +Details of the Mend Renovate Cloud versions are shown in the table below. + +| | Mend Renovate Community Cloud (Free) | Mend Renovate Enterprise Cloud | +| ----------------------------- | ------------------------------------ | ------------------------------ | +| Concurrent jobs per Org | 1 | 16 | +| Job scheduling (active repos) | Every 4 hours | Hourly (\*1) | +| Job runner CPUs | 1 CPU | 2 CPU | +| Job runner Memory | 2Gb | 8Gb | +| Job runner Disk space | 15Gb | 40Gb | +| Job timeout | 30 minutes | 60 minutes | +| Merge Confidence Workflows | Not included | Included | +| Mend.io Helpdesk Support | Not included | Included | + +(1) Bitbucket repositories running Mend Renovate Enterprise are scheduled to run every 4 hours, to avoid hitting rate limits on GitHub APIs. + +**Mend Renovate Community Cloud (Free)** - Available for free for all repositories. + +**Mend Renovate Enterprise Cloud** - Supported premium version. Contact Mend at [sales@mend.io](mailto:sales@mend.io) for purchase details. + + +!!! note + OSS-licensed orgs can request increased resources on Mend Renovate Community Cloud. + To request increased resources, create a “[Suggest an Idea](https://github.com/renovatebot/renovate/discussions/categories/suggest-an-idea)” item on the Renovate discussions board on GitHub. + Acceptance is at the discretion of Mend.io. diff --git a/docs/usage/mend-hosted/renovate-plans.md b/docs/usage/mend-hosted/renovate-plans.md deleted file mode 100644 index e946575399b5cb..00000000000000 --- a/docs/usage/mend-hosted/renovate-plans.md +++ /dev/null @@ -1,53 +0,0 @@ -# Renovate Plans on Mend-Hosted Apps - -Mend provides cloud hosting services for running Renovate on repositories hosted on the following cloud platforms: - -- GitHub -- Bitbucket Cloud -- Azure DevOps - -Mend Cloud will regularly schedule Renovate jobs against all installed repositories. -It also listens to webhooks and enqueues a Renovate job when relevant changes occur in a repo, or when actions are triggered from the Renovate PRs or Dashboard issue. -There is a web UI with functionality to view and interact with installed repositories, their jobs and job logs. - -## Accessing Mend Cloud via the Web UI - -Users can access the cloud-hosted Renovate service via the Developer Portal at [https://developer.mend.io/](https://developer.mend.io/). -Developers can log in with OAuth credentials from their cloud-based Git repository. - -![Developer Portal sign-in screen](../assets/images/portal-sign-in.png) - -Features of the Developer Portal include: - -- Ability to install, uninstall and view installed repositories -- Trigger Renovate jobs to run on demand -- View logs for all Renovate jobs -- Configure settings that apply at the Org-level or Repo-level - -## Resources and Scheduling - -The plan assigned to each Org determines the resources, scheduling and concurrency of Renoate jobs. -Mend Cloud has free and paid Renovate plans. Details of the plans are shown in the table below. - -| | Community (Free) | Pioneer (Free) | OSS Select (Free) | Enterprise | -| ----------------------------- | ---------------- | -------------- | ----------------- | ------------ | -| Concurrent jobs per Org | 1 | 8 | 2 | 16 | -| Job scheduling (active repos) | Every 4 hours | Every 4 hours | Hourly | Hourly (\*1) | -| Job runner CPUs | 1 CPU | 1 CPU | 1 CPU | 2 CPU | -| Job runner Memory | 2Gb | 3.5Gb | 6Gb | 8Gb | -| Job runner Disk space | 15Gb | 15Gb | 25Gb | 40Gb | -| Job timeout | 30 minutes | 30 minutes | 60 minutes | 60 minutes | -| Merge Confidence Workflows | Not included | Not included | Included | Included | -| Mend.io Helpdesk Support | Not included | Not included | Not Included | Included | - -(1) Bitbucket repositories on the Renovate Enterprise plan are scheduled to run every 4 hours, to avoid hitting rate limits on GitHub APIs. - -### Plan descriptions - -**Community (Free)** - This plan is available for free for all repositories. - -**Pioneer (Free)** - This plan is available for a limited time for Orgs that were installed on Renovate Cloud before 2025. Users on this plan will be transitioned to other plans over time. - -**OSS Select (Free)** - This is a premium plan granted for free to selected OSS Orgs. If you would like your Org to be considered for the free OSS Select plan, create a “[Suggest an Idea](https://github.com/renovatebot/renovate/discussions/categories/suggest-an-idea)” item on the Renovate discussions board on GitHub. Acceptance is at the discretion of Mend.io. - -**Enterprise** - A supported, paid plan available for purchase through Mend.io. Contact Mend at [sales@mend.io](mailto:sales@mend.io) for purchase details. diff --git a/docs/usage/nuget.md b/docs/usage/nuget.md index ef827088a82272..e0db20ba96758e 100644 --- a/docs/usage/nuget.md +++ b/docs/usage/nuget.md @@ -47,13 +47,16 @@ You can set alternative feeds: ```json { - "nuget": { - "registryUrls": [ - "https://api.nuget.org/v3/index.json", - "https://example1.com/nuget/", - "https://example2.com/nuget/v3/index.json" - ] - } + "packageRules": [ + { + "matchDatasources": ["nuget"], + "registryUrls": [ + "https://api.nuget.org/v3/index.json", + "https://example1.com/nuget/", + "https://example2.com/nuget/v3/index.json" + ] + } + ] } ``` @@ -93,9 +96,12 @@ If a `v3` feed URL does not end with `index.json`, you must specify the version ```json { - "nuget": { - "registryUrls": ["http://myV3feed#protocolVersion=3"] - } + "packageRules": [ + { + "matchDatasources": ["nuget"], + "registryUrls": ["https://example1.com/nuget/#protocolVersion=3"] + } + ] } ``` diff --git a/docs/usage/self-hosted-configuration.md b/docs/usage/self-hosted-configuration.md index ad6970f4492bc0..2f0f680b5f6a04 100644 --- a/docs/usage/self-hosted-configuration.md +++ b/docs/usage/self-hosted-configuration.md @@ -17,17 +17,13 @@ Please also see [Self-Hosted Experimental Options](./self-hosted-experimental.md !!! note Config options with `type=string` are always non-mergeable, so `mergeable=false`. -## allowCustomCrateRegistries - -## allowPlugins - -## allowPostUpgradeCommandTemplating +## allowCommandTemplating Let's look at an example of configuring packages with existing Angular migrations. ```javascript module.exports = { - allowedPostUpgradeCommands: ['^npm ci --ignore-scripts$', '^npx ng update'], + allowedCommands: ['^npm ci --ignore-scripts$', '^npx ng update'], }; ``` @@ -58,11 +54,32 @@ npm ci --ignore-scripts npx ng update @angular/core --from=10.0.0 --to=11.0.0 --migrate-only --allow-dirty --force ``` -If you wish to disable templating because of any security or performance concern, you may set `allowPostUpgradeCommandTemplating` to `false`. -But before you disable templating completely, try the `allowedPostUpgradeCommands` config option to limit what commands are allowed to run. +If you wish to disable templating because of any security or performance concern, you may set `allowCommandTemplating` to `false`. +But before you disable templating completely, try the `allowedCommands` config option to limit what commands are allowed to run. + +This configuration option was previously named `allowPostUpgradeCommandTemplating`. + +## allowCustomCrateRegistries + +## allowPlugins ## allowScripts +## allowedCommands + +A list of regular expressions that decide which commands in `postUpgradeTasks` are allowed to run. +If this list is empty then no tasks will be executed. + +For example: + +```json +{ + "allowedCommands": ["^tslint --fix$", "^tslint --[a-z]+$"] +} +``` + +This configuration option was formerly known as `allowedPostUpgradeCommands`. + ## allowedEnv Bot administrators can allow users to configure custom environment variables within repo config. @@ -129,19 +146,6 @@ module.exports = { }; ``` -## allowedPostUpgradeCommands - -A list of regular expressions that decide which commands in `postUpgradeTasks` are allowed to run. -If this list is empty then no tasks will be executed. - -For example: - -```json -{ - "allowedPostUpgradeCommands": ["^tslint --fix$", "^tslint --[a-z]+$"] -} -``` - ## autodiscover When you enable `autodiscover`, by default, Renovate runs on _every_ repository that the bot account can access. @@ -344,6 +348,92 @@ For example, to override the default TTL of 60 minutes for the `docker` datasour } ``` +Valid codes for namespaces are as follows: + +- `changelog-bitbucket-notes@v2` +- `changelog-bitbucket-release` +- `changelog-gitea-notes@v2` +- `changelog-gitea-release` +- `changelog-github-notes@v2` +- `changelog-github-release` +- `changelog-gitlab-notes@v2` +- `changelog-gitlab-release` +- `datasource-artifactory` +- `datasource-aws-machine-image` +- `datasource-aws-rds` +- `datasource-azure-bicep-resource` +- `datasource-azure-pipelines-tasks` +- `datasource-bazel` +- `datasource-bitbucket-tags` +- `datasource-bitrise` +- `datasource-cdnjs` +- `datasource-conan` +- `datasource-conda` +- `datasource-cpan` +- `datasource-crate-metadata` +- `datasource-crate` +- `datasource-deb` +- `datasource-deno` +- `datasource-docker-architecture` +- `datasource-docker-hub-cache` +- `datasource-docker-digest` +- `datasource-docker-hub-tags` +- `datasource-docker-imageconfig` +- `datasource-docker-labels` +- `datasource-docker-releases-v2` +- `datasource-docker-tags` +- `datasource-dotnet-version` +- `datasource-endoflife-date` +- `datasource-galaxy-collection` +- `datasource-galaxy` +- `datasource-git-refs` +- `datasource-git-tags` +- `datasource-git` +- `datasource-gitea-releases` +- `datasource-gitea-tags` +- `datasource-github-release-attachments` +- `datasource-gitlab-packages` +- `datasource-gitlab-releases` +- `datasource-gitlab-tags` +- `datasource-glasskube-packages` +- `datasource-go-direct` +- `datasource-go-proxy` +- `datasource-go` +- `datasource-golang-version` +- `datasource-gradle-version` +- `datasource-helm` +- `datasource-hermit` +- `datasource-hex` +- `datasource-hexpm-bob` +- `datasource-java-version` +- `datasource-jenkins-plugins` +- `datasource-maven` +- `datasource-maven:head-requests-timeout` +- `datasource-maven:head-requests` +- `datasource-maven:metadata-xml` +- `datasource-node-version` +- `datasource-npm:data` +- `datasource-nuget-v3` +- `datasource-orb` +- `datasource-packagist` +- `datasource-pod` +- `datasource-python-version` +- `datasource-releases` +- `datasource-repology` +- `datasource-ruby-version` +- `datasource-rubygems` +- `datasource-sbt-package` +- `datasource-terraform-module` +- `datasource-terraform-provider` +- `datasource-terraform` +- `datasource-unity3d` +- `github-releases-datasource-v2` +- `github-tags-datasource-v2` +- `merge-confidence` +- `preset` +- `terraform-provider-hash` +- `url-sha256` + ## checkedBranches This array will allow you to set the names of the branches you want to rebase/create, as if you selected their checkboxes in the Dependency Dashboard issue. diff --git a/docs/usage/self-hosted-experimental.md b/docs/usage/self-hosted-experimental.md index 78ea76a020dba6..a16acf51baf1d3 100644 --- a/docs/usage/self-hosted-experimental.md +++ b/docs/usage/self-hosted-experimental.md @@ -61,6 +61,15 @@ The formula for the delay between attempts is `RENOVATE_X_GITLAB_MERGE_REQUEST_D Default value: `5` (attempts results in max. 13.75 seconds timeout). +## `RENOVATE_X_GITLAB_BRANCH_STATUS_CHECK_ATTEMPTS` + +If set to a positive integer, Renovate will use this as the number of attempts to check branch status before trying to add a status check. +The delay between attempts is `RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY` milliseconds. + +Default value: `2` (attempts results in maximum 2 seconds timeout). + +!!! warning Increasing this value too much penalizes projects that do not have defined pipelines, Renovate will systematically wait `RENOVATE_X_GITLAB_BRANCH_STATUS_CHECK_ATTEMPTS * RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY` milliseconds on these projects and slow down the Renovate analyzes. + ## `RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY` Adjust default time (in milliseconds) given to GitLab to create pipelines for a commit pushed by Renovate. diff --git a/jest.config.ts b/jest.config.ts index b709c35129c896..064eb5cb887f8b 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,17 +1,11 @@ -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'; +import { testShards } from './tools/test/shards'; +import type { JestConfig, JestShardedSubconfig } from './tools/test/types'; +import { getCoverageIgnorePatterns } from './tools/test/utils'; const ci = !!process.env.CI; -type JestConfig = JestConfigWithTsJest & { - // https://github.com/renovatebot/renovate/issues/17034 - workerIdleMemoryLimit?: string; -}; - const cpus = os.cpus(); const mem = os.totalmem(); const stats = v8.getHeapStatistics(); @@ -34,108 +28,6 @@ function jestGithubRunnerSpecs(): JestConfig { }; } -/** - * Configuration for single test shard. - */ -interface ShardConfig { - /** - * Path patterns to match against the test file paths, of two types: - * - * 1. Particular file, e.g. `lib/util/git/index.spec.ts` - * - * - File pattern MUST end with `.spec.ts` - * - This will only search for the particular test file - * - It enables coverage for the `*.ts` file with the same name, - * e.g. `lib/util/git/index.ts` - * - You probably want to use directory pattern instead - * - * 2. Whole directory, e.g. `lib/modules/datasource` - * - * - This will search for all `*.spec.ts` files under the directory - * - It enables coverage all `*.ts` files under the directory, - * e.g. `lib/modules/datasource/foo/bar/baz.ts` - */ - matchPaths: string[]; -} - -/** - * Configuration for test shards that can be run with `TEST_SHARD` environment variable. - * - * For each shard, we specify a subset of tests to run. - * The tests from previous shards are excluded from the next shard. - * - * Storing shards config in the separate file helps to form CI matrix - * using pre-installed `jq` utility. - */ -const testShards: Record = { - 'datasources-1': { - matchPaths: ['lib/modules/datasource/[a-g]*'], - }, - 'datasources-2': { - matchPaths: ['lib/modules/datasource'], - }, - 'managers-1': { - matchPaths: ['lib/modules/manager/[a-c]*'], - }, - 'managers-2': { - matchPaths: ['lib/modules/manager/[d-h]*'], - }, - 'managers-3': { - matchPaths: ['lib/modules/manager/[i-n]*'], - }, - 'managers-4': { - matchPaths: ['lib/modules/manager'], - }, - platform: { - matchPaths: ['lib/modules/platform'], - }, - versioning: { - matchPaths: ['lib/modules/versioning'], - }, - 'workers-1': { - matchPaths: [ - 'lib/workers/repository/changelog', - 'lib/workers/repository/config-migration', - 'lib/workers/repository/extract', - 'lib/workers/repository/finalize', - 'lib/workers/repository/init', - 'lib/workers/repository/model', - ], - }, - 'workers-2': { - matchPaths: [ - 'lib/workers/repository/onboarding', - 'lib/workers/repository/process', - ], - }, - 'workers-3': { - matchPaths: [ - 'lib/workers/repository/update', - 'lib/workers/repository/updates', - ], - }, - 'workers-4': { - matchPaths: ['lib/workers'], - }, - 'git-1': { - matchPaths: ['lib/util/git/index.spec.ts'], - }, - 'git-2': { - matchPaths: ['lib/util/git'], - }, - util: { - matchPaths: ['lib/util'], - }, - other: { - matchPaths: ['lib'], - }, -}; - -/** - * Subset of Jest config that is relevant for sharded test run. - */ -type JestShardedSubconfig = Pick; - /** * Convert match pattern to a form that matches on file with `.ts` or `.spec.ts` extension. */ @@ -247,212 +139,8 @@ const config: JestConfig = { export default config; -type RunsOn = 'ubuntu-latest' | 'windows-latest' | 'macos-latest'; - -interface ShardGroup { - /** - * Input for `runs-on` field. - */ - os: RunsOn; - - /** - * Controls whether coverage is collected for this shard group. - */ - coverage: boolean; - - /** - * Input for `name` field. - */ - name: string; - - /** - * Space-separated list of shard keys, it's - * meant to be inserted into bash for-loop. - */ - shards: string; - - /** - * It's meant to be used for Jest caching. - */ - 'cache-key': string; - - /** - * It's used to set test runner timeout. - */ - 'runner-timeout-minutes': number; - - /** - * It's used to set `--test-timeout` Jest CLI flag. - */ - 'test-timeout-milliseconds': number; - - /** - * It's used as the name for coverage artifact. - */ - 'upload-artifact-name': string; -} - -/** - * Given the file list affected by commit, return the list - * of shards that test these changes. - */ -function getMatchingShards(files: string[]): string[] { - const matchingShards = new Set(); - for (const file of files) { - for (const [key, { matchPaths }] of Object.entries(testShards)) { - const patterns = matchPaths.map((path) => - path.endsWith('.spec.ts') - ? path.replace(/\.spec\.ts$/, '{.ts,.spec.ts}') - : `${path}/**/*`, - ); - - if (patterns.some((pattern) => minimatch(file, pattern))) { - matchingShards.add(key); - break; - } - } - } - - return Object.keys(testShards).filter((shard) => matchingShards.has(shard)); -} - -/** - * Distribute items evenly across runner instances. - */ -function scheduleItems(items: T[], availableInstances: number): T[][] { - const numInstances = Math.min(items.length, availableInstances); - const maxPerInstance = Math.ceil(items.length / numInstances); - const lighterInstancesIdx = - items.length % numInstances === 0 - ? numInstances - : items.length % numInstances; - - const partitionSizes = Array.from({ length: numInstances }, (_, idx) => - idx < lighterInstancesIdx ? maxPerInstance : maxPerInstance - 1, - ); - - const result: T[][] = Array.from({ length: numInstances }, () => []); - let rest = items.slice(); - for (let idx = 0; idx < numInstances; idx += 1) { - const partitionSize = partitionSizes[idx]; - const partition = rest.slice(0, partitionSize); - result[idx] = partition; - rest = rest.slice(partitionSize); - } - - return result; -} - -/** - * If `SCHEDULE_TEST_SHARDS` env variable is set, it means we're in `setup` CI job. - * We don't want to see anything except key-value pairs in the output. - * Otherwise, we're printing useful stats. - */ -if (process.env.SCHEDULE_TEST_SHARDS) { - let shardKeys = Object.keys(testShards); - - if (process.env.FILTER_SHARDS === 'true' && process.env.CHANGED_FILES) { - try { - const changedFiles: string[] = JSON.parse(process.env.CHANGED_FILES); - const matchingShards = getMatchingShards(changedFiles); - if (matchingShards.length === 0) { - // eslint-disable-next-line no-console - console.log(`test-matrix-empty=true`); - process.exit(0); - } - shardKeys = shardKeys.filter((key) => matchingShards.includes(key)); - } catch (err) { - // eslint-disable-next-line no-console - console.error(err); - process.exit(1); - } - } - - /** - * Not all runners are created equal. - * Minutes cost proportion is 1:2:10 for Ubuntu:Windows:MacOS. - * - * Although it's free in our case, - * we can't run as many Windows and MacOS runners as we want. - * - * Because of this, we partition shards into groups, given that: - * - There are 16 shards in total - * - We can't run more than 10 Windows runners - * - We can't run more than 5 MacOS runners - */ - const shardGrouping: Record = { - 'ubuntu-latest': scheduleItems(shardKeys, 16), - }; - - if (process.env.ALL_PLATFORMS === 'true') { - // shardGrouping['windows-latest'] = scheduleItems(shardKeys, 8); - shardGrouping['macos-latest'] = scheduleItems(shardKeys, 4); - } - - const shardGroups: ShardGroup[] = []; - for (const [os, groups] of Object.entries(shardGrouping)) { - const coverage = os === 'ubuntu-latest'; - - const total = groups.length; - for (let idx = 0; idx < groups.length; idx += 1) { - const number = idx + 1; - const platform = os.replace(/-latest$/, ''); - const name = - platform === 'ubuntu' - ? `test (${number}/${total})` - : `test-${platform} (${number}/${total})`; - - const shards = groups[idx]; - const cacheKey = crypto - .createHash('md5') - .update(shards.join(':')) - .digest('hex'); - - const runnerTimeoutMinutes = - { - ubuntu: 10, - windows: 20, - macos: 20, - }[platform] ?? 20; - - const testTimeoutMilliseconds = - { - windows: 240000, - }[platform] ?? 120000; - - shardGroups.push({ - os: os as RunsOn, - coverage, - name, - shards: shards.join(' '), - 'cache-key': cacheKey, - 'runner-timeout-minutes': runnerTimeoutMinutes, - 'test-timeout-milliseconds': testTimeoutMilliseconds, - 'upload-artifact-name': `coverage-${shards.sort().join('_')}`, - }); - } - } - - /** - * Output will be consumed by `setup` CI job. - */ - // eslint-disable-next-line no-console - console.log(`test-shard-matrix=${JSON.stringify(shardGroups)}`); - - process.exit(0); -} - process.stderr.write(`Host stats: Cpus: ${cpus.length} 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/global.ts b/lib/config/global.ts index 295fb4f394e44c..928f1c17939d6d 100644 --- a/lib/config/global.ts +++ b/lib/config/global.ts @@ -3,12 +3,12 @@ import type { RenovateConfig, RepoGlobalConfig } from './types'; export class GlobalConfig { // TODO: once global config work is complete, add a test to make sure this list includes all options with globalOnly=true (#9603) private static readonly OPTIONS: (keyof RepoGlobalConfig)[] = [ + 'allowedCommands', 'allowedEnv', + 'allowCommandTemplating', 'allowCustomCrateRegistries', 'allowedHeaders', - 'allowedPostUpgradeCommands', 'allowPlugins', - 'allowPostUpgradeCommandTemplating', 'allowScripts', 'binarySource', 'cacheDir', diff --git a/lib/config/index.spec.ts b/lib/config/index.spec.ts index 13fd4ac4fc995b..9cc5c378ba919c 100644 --- a/lib/config/index.spec.ts +++ b/lib/config/index.spec.ts @@ -125,7 +125,7 @@ describe('config/index', () => { const parentConfig = { ...defaultConfig }; const config = getManagerConfig(parentConfig, 'npm'); expect(config).toContainEntries([ - ['fileMatch', ['(^|/)package\\.json$']], + ['fileMatch', ['(^|/)package\\.json$', '(^|/)pnpm-workspace\\.yaml$']], ]); expect(getManagerConfig(parentConfig, 'html')).toContainEntries([ ['fileMatch', ['\\.html?$']], diff --git a/lib/config/migrations/migrations-service.ts b/lib/config/migrations/migrations-service.ts index 66eacdb0d34fcb..2dfb84d5123fbe 100644 --- a/lib/config/migrations/migrations-service.ts +++ b/lib/config/migrations/migrations-service.ts @@ -82,6 +82,8 @@ export class MigrationsService { static readonly renamedProperties: ReadonlyMap = new Map([ ['adoptium-java', 'java-version'], + ['allowPostUpgradeCommandTemplating', 'allowCommandTemplating'], + ['allowedPostUpgradeCommands', 'allowedCommands'], ['azureAutoApprove', 'autoApprove'], ['customChangelogUrl', 'changelogUrl'], ['endpoints', 'hostRules'], diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts index b3e4cfa980501b..c5ad124c28110c 100644 --- a/lib/config/options/index.ts +++ b/lib/config/options/index.ts @@ -108,7 +108,7 @@ const options: RenovateOptions[] = [ globalOnly: true, }, { - name: 'allowPostUpgradeCommandTemplating', + name: 'allowCommandTemplating', description: 'Set this to `false` to disable template compilation for post-upgrade commands.', type: 'boolean', @@ -116,9 +116,9 @@ const options: RenovateOptions[] = [ globalOnly: true, }, { - name: 'allowedPostUpgradeCommands', + name: 'allowedCommands', description: - 'A list of regular expressions that decide which post-upgrade tasks are allowed.', + 'A list of regular expressions that decide which commands are allowed in post-upgrade tasks.', type: 'array', subType: 'string', default: [], @@ -516,7 +516,7 @@ const options: RenovateOptions[] = [ description: 'Change this value to override the default Renovate sidecar image.', type: 'string', - default: 'ghcr.io/containerbase/sidecar:13.5.8', + default: 'ghcr.io/containerbase/sidecar:13.7.2', globalOnly: true, }, { @@ -2735,18 +2735,26 @@ const options: RenovateOptions[] = [ description: 'Custom manager to use. Valid only within a `customManagers` object.', type: 'string', - allowedValues: ['regex'], + allowedValues: ['jsonata', 'regex'], parents: ['customManagers'], cli: false, env: false, }, { - name: 'matchStrings', + name: 'fileFormat', description: - 'Regex capture rule to use. Valid only within a `customManagers` object.', + 'It specifies the syntax of the package file being managed by the custom JSONata manager.', + type: 'string', + allowedValues: ['json', 'yaml'], + parents: ['customManagers'], + cli: false, + env: false, + }, + { + name: 'matchStrings', + description: 'Queries to use. Valid only within a `customManagers` object.', type: 'array', subType: 'string', - format: 'regex', parents: ['customManagers'], cli: false, env: false, diff --git a/lib/config/presets/__snapshots__/index.spec.ts.snap b/lib/config/presets/__snapshots__/index.spec.ts.snap index bd3fea422a76f5..ce57b9dfbee0c6 100644 --- a/lib/config/presets/__snapshots__/index.spec.ts.snap +++ b/lib/config/presets/__snapshots__/index.spec.ts.snap @@ -34,7 +34,7 @@ exports[`config/presets/index resolvePreset migrates automerge in presets 1`] = "Add the \`renovate/\` prefix to all branch names.", "Use semantic commit type \`fix\` for dependencies and \`chore\` for all others if semantic commits are in use.", "Require all status checks to pass before any automerging.", - "Pin dependency versions for \`devDependencies\` and retain SemVer ranges for others.", + "Pin dependency versions for development dependencies and retain SemVer ranges for others.", ], "ignoreTests": false, "ignoreUnstable": true, @@ -111,6 +111,8 @@ exports[`config/presets/index resolvePreset migrates automerge in presets 1`] = { "matchDepTypes": [ "devDependencies", + "dev-dependencies", + "dev", ], "rangeStrategy": "pin", }, diff --git a/lib/config/presets/github/index.ts b/lib/config/presets/github/index.ts index 06cca040ac9483..a9063233107ec3 100644 --- a/lib/config/presets/github/index.ts +++ b/lib/config/presets/github/index.ts @@ -24,7 +24,7 @@ export async function fetchJSONFile( logger.trace({ url }, `Preset URL`); let res: { body: { content: string } }; try { - res = await http.getJson(url); + res = await http.getJsonUnchecked(url); } catch (err) { // istanbul ignore if: not testable with nock if (err instanceof ExternalHostError) { diff --git a/lib/config/presets/gitlab/index.ts b/lib/config/presets/gitlab/index.ts index 300bf3e51ff5ee..2af74c99d094cf 100644 --- a/lib/config/presets/gitlab/index.ts +++ b/lib/config/presets/gitlab/index.ts @@ -14,7 +14,7 @@ async function getDefaultBranchName( urlEncodedPkgName: string, endpoint: string, ): Promise { - const res = await gitlabApi.getJson( + const res = await gitlabApi.getJsonUnchecked( `${endpoint}projects/${urlEncodedPkgName}`, ); return res.body.default_branch ?? 'master'; // should never happen, but we keep this to ensure the current behavior diff --git a/lib/config/presets/index.ts b/lib/config/presets/index.ts index 349db47089fe75..b482521ff41d59 100644 --- a/lib/config/presets/index.ts +++ b/lib/config/presets/index.ts @@ -7,7 +7,7 @@ import { logger } from '../../logger'; import { ExternalHostError } from '../../types/errors/external-host-error'; import * as memCache from '../../util/cache/memory'; import * as packageCache from '../../util/cache/package'; -import { getTtlOverride } from '../../util/cache/package/decorator'; +import { getTtlOverride } from '../../util/cache/package/ttl'; import { clone } from '../../util/clone'; import { regEx } from '../../util/regex'; import * as template from '../../util/template'; diff --git a/lib/config/presets/internal/custom-managers.ts b/lib/config/presets/internal/custom-managers.ts index 5a3fc8b244d53c..63ceefda7117a2 100644 --- a/lib/config/presets/internal/custom-managers.ts +++ b/lib/config/presets/internal/custom-managers.ts @@ -24,7 +24,7 @@ export const presets: Record = { 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', + '# renovate: datasource=(?[a-zA-Z0-9-._]+?) depName=(?[^\\s]+?)(?: (lookupName|packageName)=(?[^\\s]+?))?(?: versioning=(?[^\\s]+?))?(?: extractVersion=(?[^\\s]+?))?(?: registryUrl=(?[^\\s]+?))?\\s+.*\\s+[A-Za-z0-9_]+?_VERSION[ =:]\\s?["\']?(?.+?)["\']?\\s', ], }, ], @@ -39,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)\\s+[A-Za-z0-9_]+?_VERSION[ =]["\']?(?.+?)["\']?\\s', + '# renovate: datasource=(?[a-zA-Z0-9-._]+?) depName=(?[^\\s]+?)(?: (lookupName|packageName)=(?[^\\s]+?))?(?: versioning=(?[^\\s]+?))?(?: extractVersion=(?[^\\s]+?))?(?: registryUrl=(?[^\\s]+?))?\\s(?:ENV|ARG)\\s+[A-Za-z0-9_]+?_VERSION[ =]["\']?(?.+?)["\']?\\s', ], }, ], @@ -54,7 +54,7 @@ export const presets: Record = { '(^|/)action\\.ya?ml$', ], matchStrings: [ - '# renovate: datasource=(?[a-z-.]+?) depName=(?[^\\s]+?)(?: (?:lookupName|packageName)=(?[^\\s]+?))?(?: versioning=(?[^\\s]+?))?(?: extractVersion=(?[^\\s]+?))?\\s+[A-Za-z0-9_]+?_VERSION\\s*:\\s*["\']?(?.+?)["\']?\\s', + '# renovate: datasource=(?[a-zA-Z0-9-._]+?) depName=(?[^\\s]+?)(?: (?:lookupName|packageName)=(?[^\\s]+?))?(?: versioning=(?[^\\s]+?))?(?: extractVersion=(?[^\\s]+?))?\\s+[A-Za-z0-9_]+?_VERSION\\s*:\\s*["\']?(?.+?)["\']?\\s', ], }, ], @@ -67,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]+?))?(?: registryUrl=(?[^\\s]+?))?\\s+[A-Za-z0-9_]+?_VERSION\\s*:\\s*["\']?(?.+?)["\']?\\s', + '# renovate: datasource=(?[a-zA-Z0-9-._]+?) depName=(?[^\\s]+?)(?: (?:packageName)=(?[^\\s]+?))?(?: versioning=(?[^\\s]+?))?(?: extractVersion=(?[^\\s]+?))?(?: registryUrl=(?[^\\s]+?))?\\s+[A-Za-z0-9_]+?_VERSION\\s*:\\s*["\']?(?.+?)["\']?\\s', ], }, ], @@ -98,7 +98,7 @@ export const presets: Record = { '\\.mk$', ], matchStrings: [ - '# renovate: datasource=(?[a-z-.]+?) depName=(?[^\\s]+?)(?: (?:packageName)=(?[^\\s]+?))?(?: versioning=(?[^\\s]+?))?(?: extractVersion=(?[^\\s]+?))?(?: registryUrl=(?[^\\s]+?))?\\s+[A-Za-z0-9_]+?_VERSION\\s*:*\\??=\\s*["\']?(?.+?)["\']?\\s', + '# renovate: datasource=(?[a-zA-Z0-9-._]+?) depName=(?[^\\s]+?)(?: (?:packageName)=(?[^\\s]+?))?(?: versioning=(?[^\\s]+?))?(?: extractVersion=(?[^\\s]+?))?(?: registryUrl=(?[^\\s]+?))?\\s+[A-Za-z0-9_]+?_VERSION\\s*:*\\??=\\s*["\']?(?.+?)["\']?\\s', ], }, ], @@ -112,7 +112,7 @@ export const presets: Record = { '{{#if datasource}}{{{datasource}}}{{else}}maven{{/if}}', fileMatch: ['(^|/)pom\\.xml$'], matchStrings: [ - '\\s+<.+\\.version>(?.+)<\\/.+\\.version>', + '\\s+<.+\\.version>(?.+)<\\/.+\\.version>', ], versioningTemplate: '{{#if versioning}}{{{versioning}}}{{/if}}', }, diff --git a/lib/config/presets/internal/default.ts b/lib/config/presets/internal/default.ts index a2ab86a2c3c799..e567f1168e15e3 100644 --- a/lib/config/presets/internal/default.ts +++ b/lib/config/presets/internal/default.ts @@ -141,11 +141,11 @@ export const presets: Record = { description: 'Disable Renovate Dependency Dashboard creation.', }, disableDevDependencies: { - description: 'Do not update `devDependencies` versions/ranges.', + description: 'Do not update development dependencies.', packageRules: [ { enabled: false, - matchDepTypes: ['devDependencies'], + matchDepTypes: ['devDependencies', 'dev-dependencies', 'dev'], }, ], }, @@ -408,10 +408,10 @@ export const presets: Record = { ], }, pinDevDependencies: { - description: 'Pin dependency versions for `devDependencies`.', + description: 'Pin dependency versions for development dependencies.', packageRules: [ { - matchDepTypes: ['devDependencies'], + matchDepTypes: ['devDependencies', 'dev-dependencies', 'dev'], rangeStrategy: 'pin', }, ], @@ -422,14 +422,14 @@ export const presets: Record = { }, pinOnlyDevDependencies: { description: - 'Pin dependency versions for `devDependencies` and retain SemVer ranges for others.', + 'Pin dependency versions for development dependencies and retain SemVer ranges for others.', packageRules: [ { matchPackageNames: ['*'], rangeStrategy: 'replace', }, { - matchDepTypes: ['devDependencies'], + matchDepTypes: ['devDependencies', 'dev-dependencies', 'dev'], rangeStrategy: 'pin', }, { diff --git a/lib/config/presets/internal/global.ts b/lib/config/presets/internal/global.ts index ce8240fcf131d8..a541fad0b910b6 100644 --- a/lib/config/presets/internal/global.ts +++ b/lib/config/presets/internal/global.ts @@ -4,7 +4,7 @@ import type { Preset } from '../types'; export const presets: Record = { safeEnv: { - allowedEnv: ['GO*', 'RUSTC_BOOTSTRAP'], + allowedEnv: ['GO*', 'GRADLE_OPTS', 'RUSTC_BOOTSTRAP'], description: 'Hopefully safe environment variables to allow users to configure.', }, diff --git a/lib/config/presets/internal/workarounds.ts b/lib/config/presets/internal/workarounds.ts index df7c8ac1735bdc..b1038a2cd6488f 100644 --- a/lib/config/presets/internal/workarounds.ts +++ b/lib/config/presets/internal/workarounds.ts @@ -24,6 +24,7 @@ export const presets: Record = { 'workarounds:k3sKubernetesVersioning', 'workarounds:rke2KubernetesVersioning', 'workarounds:libericaJdkDockerVersioning', + 'workarounds:ubuntuDockerVersioning', ], ignoreDeps: [], // Hack to improve onboarding PR description }, @@ -292,4 +293,14 @@ export const presets: Record = { }, ], }, + ubuntuDockerVersioning: { + description: 'Use ubuntu versioning for `ubuntu` docker images.', + packageRules: [ + { + matchDatasources: ['docker'], + matchDepNames: ['ubuntu'], + versioning: 'ubuntu', + }, + ], + }, }; diff --git a/lib/config/presets/npm/index.ts b/lib/config/presets/npm/index.ts index c01c5c70e0f173..e50201915c0812 100644 --- a/lib/config/presets/npm/index.ts +++ b/lib/config/presets/npm/index.ts @@ -31,7 +31,7 @@ export async function getPreset({ 'Using npm packages for Renovate presets is now deprecated. Please migrate to repository-based presets instead.', ); const packageUrl = resolvePackageUrl(registryUrl, pkg); - const body = (await http.getJson(packageUrl)).body; + const body = (await http.getJsonUnchecked(packageUrl)).body; // TODO: check null #22198 dep = body.versions![body['dist-tags']!.latest]; } catch { diff --git a/lib/config/types.ts b/lib/config/types.ts index 0798f99d7ca662..d3c38f602112a3 100644 --- a/lib/config/types.ts +++ b/lib/config/types.ts @@ -6,6 +6,7 @@ import type { HostRule, SkipReason } from '../types'; import type { StageName } from '../types/skip-reason'; import type { GitNoVerifyOption } from '../util/git/types'; import type { MergeConfidence } from '../util/merge-confidence/types'; +import type { Timestamp } from '../util/timestamp'; export type RenovateConfigStage = | 'global' @@ -131,13 +132,13 @@ export interface GlobalOnlyConfig { // Config options used within the repository worker, but not user configurable // The below should contain config options where globalOnly=true export interface RepoGlobalConfig { + allowedCommands?: string[]; + allowCommandTemplating?: boolean; allowCustomCrateRegistries?: boolean; allowPlugins?: boolean; - allowPostUpgradeCommandTemplating?: boolean; allowScripts?: boolean; allowedEnv?: string[]; allowedHeaders?: string[]; - allowedPostUpgradeCommands?: string[]; binarySource?: 'docker' | 'global' | 'install' | 'hermit'; cacheDir?: string; cacheHardTtlMinutes?: number; @@ -271,6 +272,7 @@ export interface RenovateConfig packageFile?: string; packageRules?: PackageRule[]; postUpdateOptions?: string[]; + branchConcurrentLimit?: number | null; prConcurrentLimit?: number; prHourlyLimit?: number; forkModeDisallowMaintainerEdits?: boolean; @@ -309,6 +311,7 @@ export interface RenovateConfig branchTopic?: string; additionalBranchPrefix?: string; + sharedVariableName?: string; } const CustomDatasourceFormats = ['json', 'plain', 'yaml', 'html'] as const; @@ -550,7 +553,7 @@ export interface PackageRuleInputConfig extends Record { manager?: string; datasource?: string; packageRules?: (PackageRule & PackageRuleInputConfig)[]; - releaseTimestamp?: string | null; + releaseTimestamp?: Timestamp | null; repository?: string; currentVersionAgeInDays?: number; currentVersionTimestamp?: string; diff --git a/lib/config/validation-helpers/utils.ts b/lib/config/validation-helpers/utils.ts index 5d676bed324800..ac428376027c59 100644 --- a/lib/config/validation-helpers/utils.ts +++ b/lib/config/validation-helpers/utils.ts @@ -1,9 +1,8 @@ import is from '@sindresorhus/is'; +import jsonata from 'jsonata'; import { logger } from '../../logger'; -import type { - RegexManagerConfig, - RegexManagerTemplates, -} from '../../modules/manager/custom/regex/types'; +import type { RegexManagerTemplates } from '../../modules/manager/custom/regex/types'; +import type { CustomManager } from '../../modules/manager/custom/types'; import { regEx } from '../../util/regex'; import type { ValidationMessage } from '../types'; @@ -78,21 +77,20 @@ export function isFalseGlobal( return false; } -function hasField( - customManager: Partial, - field: string, -): boolean { +function hasField(customManager: CustomManager, field: string): boolean { const templateField = `${field}Template` as keyof RegexManagerTemplates; + const fieldStr = + customManager.customType === 'regex' ? `(?<${field}>` : field; return !!( customManager[templateField] ?? customManager.matchStrings?.some((matchString) => - matchString.includes(`(?<${field}>`), + matchString.includes(fieldStr), ) ); } export function validateRegexManagerFields( - customManager: Partial, + customManager: CustomManager, currentPath: string, errors: ValidationMessage[], ): void { @@ -114,7 +112,8 @@ export function validateRegexManagerFields( } else { errors.push({ topic: 'Configuration Error', - message: `Each Custom Manager must contain a non-empty matchStrings array`, + message: + 'Each Custom Manager `matchStrings` array must have at least one item.', }); } @@ -136,3 +135,56 @@ export function validateRegexManagerFields( }); } } + +export function validateJSONataManagerFields( + customManager: CustomManager, + currentPath: string, + errors: ValidationMessage[], +): void { + if (!is.nonEmptyString(customManager.fileFormat)) { + errors.push({ + topic: 'Configuration Error', + message: 'Each JSONata manager must contain a fileFormat field.', + }); + } + + if (is.nonEmptyArray(customManager.matchStrings)) { + for (const matchString of customManager.matchStrings) { + try { + jsonata(matchString); + } catch (err) { + logger.debug( + { err }, + 'customManager.matchStrings JSONata query validation error', + ); + errors.push({ + topic: 'Configuration Error', + message: `Invalid JSONata query for ${currentPath}: \`${matchString}\``, + }); + } + } + } else { + errors.push({ + topic: 'Configuration Error', + message: `Each Custom Manager must contain a non-empty matchStrings array`, + }); + } + + const mandatoryFields = ['currentValue', 'datasource']; + for (const field of mandatoryFields) { + if (!hasField(customManager, field)) { + errors.push({ + topic: 'Configuration Error', + message: `JSONata Managers must contain ${field}Template configuration or ${field} in the query `, + }); + } + } + + const nameFields = ['depName', 'packageName']; + if (!nameFields.some((field) => hasField(customManager, field))) { + errors.push({ + topic: 'Configuration Error', + message: `JSONata Managers must contain depName or packageName in the query or their templates`, + }); + } +} diff --git a/lib/config/validation.spec.ts b/lib/config/validation.spec.ts index 927650a07d732d..4cf3e39eab28c3 100644 --- a/lib/config/validation.spec.ts +++ b/lib/config/validation.spec.ts @@ -706,7 +706,8 @@ describe('config/validation', () => { currentValueTemplate: 'baz', }, { - customType: 'regex', + customType: 'jsonata', + fileFormat: 'json', fileMatch: ['foo'], depNameTemplate: 'foo', datasourceTemplate: 'bar', @@ -724,7 +725,7 @@ describe('config/validation', () => { expect(errors).toMatchInlineSnapshot(` [ { - "message": "Each Custom Manager must contain a non-empty matchStrings array", + "message": "Each Custom Manager \`matchStrings\` array must have at least one item.", "topic": "Configuration Error", }, { @@ -776,6 +777,60 @@ describe('config/validation', () => { expect(errors).toHaveLength(1); }); + it('error if no fileFormat in custom JSONata manager', async () => { + const config: RenovateConfig = { + customManagers: [ + { + customType: 'jsonata', + fileMatch: ['package.json'], + matchStrings: [ + 'packages.{"depName": name, "currentValue": version, "datasource": "npm"}', + ], + }, + ], + }; + const { warnings, errors } = await configValidation.validateConfig( + 'repo', + config, + true, + ); + expect(warnings).toHaveLength(0); + expect(errors).toMatchObject([ + { + topic: 'Configuration Error', + message: 'Each JSONata manager must contain a fileFormat field.', + }, + ]); + }); + + it('validates JSONata query for each matchStrings', async () => { + const config: RenovateConfig = { + customManagers: [ + { + customType: 'jsonata', + fileFormat: 'json', + fileMatch: ['package.json'], + matchStrings: ['packages.{'], + depNameTemplate: 'foo', + datasourceTemplate: 'bar', + currentValueTemplate: 'baz', + }, + ], + }; + const { warnings, errors } = await configValidation.validateConfig( + 'repo', + config, + true, + ); + expect(warnings).toHaveLength(0); + expect(errors).toMatchObject([ + { + topic: 'Configuration Error', + message: `Invalid JSONata query for customManagers: \`packages.{\``, + }, + ]); + }); + // testing if we get all errors at once or not (possible), this does not include customType or fileMatch // since they are common to all custom managers it('validates all possible regex manager options', async () => { @@ -811,14 +866,12 @@ describe('config/validation', () => { depTypeTemplate: 'apple', }, { - customType: 'regex', - fileMatch: ['Dockerfile'], - matchStrings: ['ENV (?.*?)\\s'], - packageNameTemplate: 'foo', - datasourceTemplate: 'bar', - registryUrlTemplate: 'foobar', - extractVersionTemplate: '^(?v\\d+\\.\\d+)', - depTypeTemplate: 'apple', + customType: 'jsonata', + fileFormat: 'json', + fileMatch: ['package.json'], + matchStrings: [ + 'packages.{"depName": depName, "currentValue": version, "datasource": "npm"}', + ], }, ], }; @@ -876,6 +929,39 @@ describe('config/validation', () => { expect(errors).toHaveLength(1); }); + it('errors if customManager fields are missing: JSONataManager', async () => { + const config: RenovateConfig = { + customManagers: [ + { + customType: 'jsonata', + fileFormat: 'json', + fileMatch: ['package.json'], + matchStrings: ['packages'], + }, + ], + }; + const { warnings, errors } = await configValidation.validateConfig( + 'repo', + config, + true, + ); + expect(warnings).toHaveLength(0); + expect(errors).toMatchObject([ + { + topic: 'Configuration Error', + message: `JSONata Managers must contain currentValueTemplate configuration or currentValue in the query `, + }, + { + topic: 'Configuration Error', + message: `JSONata Managers must contain datasourceTemplate configuration or datasource in the query `, + }, + { + topic: 'Configuration Error', + message: `JSONata Managers must contain depName or packageName in the query or their templates`, + }, + ]); + }); + it('ignore keys', async () => { const config = { $schema: 'renovate.json', @@ -1764,7 +1850,7 @@ describe('config/validation', () => { it('validates array type options', async () => { const config = { - allowedPostUpgradeCommands: ['cmd'], + allowedCommands: ['cmd'], checkedBranches: 'invalid-type', gitNoVerify: ['invalid'], mergeConfidenceDatasources: [1], diff --git a/lib/config/validation.ts b/lib/config/validation.ts index 551c07ba9f0863..c89f07f4ee8e59 100644 --- a/lib/config/validation.ts +++ b/lib/config/validation.ts @@ -37,6 +37,7 @@ import * as regexOrGlobValidator from './validation-helpers/regex-glob-matchers' import { getParentName, isFalseGlobal, + validateJSONataManagerFields, validateNumber, validatePlainObject, validateRegexManagerFields, @@ -486,6 +487,7 @@ export async function validateConfig( const allowedKeys = [ 'customType', 'description', + 'fileFormat', 'fileMatch', 'matchStrings', 'matchStringsStrategy', @@ -527,6 +529,13 @@ export async function validateConfig( errors, ); break; + case 'jsonata': + validateJSONataManagerFields( + customManager, + currentPath, + errors, + ); + break; } } else { errors.push({ diff --git a/lib/constants/category.ts b/lib/constants/category.ts index a92c54656eeb56..94c22015c9a5cd 100644 --- a/lib/constants/category.ts +++ b/lib/constants/category.ts @@ -12,6 +12,7 @@ export const Categories = [ 'dotnet', 'elixir', 'golang', + 'haskell', 'helm', 'iac', 'java', diff --git a/lib/data/monorepo.json b/lib/data/monorepo.json index ebaf39de7f8100..e35e42f1af885d 100644 --- a/lib/data/monorepo.json +++ b/lib/data/monorepo.json @@ -26,6 +26,7 @@ "arcus.observability": "https://github.com/arcus-azure/arcus.observability", "arcus.security": "https://github.com/arcus-azure/arcus.security", "arcus.webapi": "https://github.com/arcus-azure/arcus.webapi", + "arrow-kt": "https://github.com/arrow-kt/arrow", "aspire": "https://github.com/dotnet/aspire", "aspnet aspnetwebstack": "https://github.com/aspnet/AspNetWebStack", "aspnet extensions": "https://github.com/aspnet/Extensions", @@ -60,6 +61,22 @@ "azure azure-libraries-for-net": "https://github.com/Azure/azure-libraries-for-net", "azure azure-sdk-for-net": "https://github.com/Azure/azure-sdk-for-net", "azure azure-storage-net": "https://github.com/Azure/azure-storage-net", + "azure-ad-microsoft-authentication-library-for-android": "https://github.com/AzureAD/microsoft-authentication-library-for-android", + "azure-ad-microsoft-authentication-library-for-dotnet": "https://github.com/AzureAD/microsoft-authentication-library-for-dotnet", + "azure-ad-microsoft-authentication-library-for-go": "https://github.com/AzureAD/microsoft-authentication-library-for-go", + "azure-ad-microsoft-authentication-library-for-java": "https://github.com/AzureAD/microsoft-authentication-library-for-java", + "azure-ad-microsoft-authentication-library-for-js": "https://github.com/AzureAD/microsoft-authentication-library-for-js", + "azure-ad-microsoft-authentication-library-for-objc": "https://github.com/AzureAD/microsoft-authentication-library-for-objc", + "azure-ad-microsoft-authentication-library-for-python": "https://github.com/AzureAD/microsoft-authentication-library-for-python", + "azure-sdk-for-android": "https://github.com/Azure/azure-sdk-for-android", + "azure-sdk-for-c": "https://github.com/Azure/azure-sdk-for-c", + "azure-sdk-for-cpp": "https://github.com/Azure/azure-sdk-for-cpp", + "azure-sdk-for-go": "https://github.com/Azure/azure-sdk-for-go", + "azure-sdk-for-ios": "https://github.com/Azure/azure-sdk-for-ios", + "azure-sdk-for-java": "https://github.com/Azure/azure-sdk-for-java", + "azure-sdk-for-js": "https://github.com/Azure/azure-sdk-for-js", + "azure-sdk-for-python": "https://github.com/Azure/azure-sdk-for-python", + "azure-sdk-for-rust": "https://github.com/Azure/azure-sdk-for-rust", "babel": "https://github.com/babel/babel", "backstage": "https://github.com/backstage/backstage", "baset": "https://github.com/igmat/baset", @@ -325,6 +342,7 @@ "happy-dom": "https://github.com/capricorn86/happy-dom", "Hangfire": "https://github.com/HangfireIO/Hangfire", "hickory-dns": "https://github.com/hickory-dns/hickory-dns", + "html-eslint": "https://github.com/yeonjuan/html-eslint", "infrastructure-ui": "https://github.com/instructure/instructure-ui", "ionic-native": "https://github.com/ionic-team/ionic-native", "istanbuljs": "https://github.com/istanbuljs/istanbuljs", @@ -356,6 +374,7 @@ "kotlin": "https://github.com/JetBrains/kotlin", "kotlinx-coroutines": "https://github.com/Kotlin/kotlinx.coroutines", "kroki": "https://github.com/yuzutech/kroki", + "ksp": "https://github.com/google/ksp", "ktor": "https://github.com/ktorio/ktor", "lamar": "https://github.com/JasperFx/lamar", "lerna": "https://github.com/lerna/lerna", @@ -392,6 +411,7 @@ "mstest": "https://github.com/microsoft/testfx", "mutation-testing-elements": "https://github.com/stryker-mutator/mutation-testing-elements", "nest": [ + "https://github.com/nestjs/config", "https://github.com/nestjs/nest", "https://github.com/nestjs/nest-cli", "https://github.com/nestjs/passport", @@ -459,6 +479,7 @@ "prisma": "https://github.com/prisma/prisma", "prometheus-net": "https://github.com/prometheus-net/prometheus-net", "promster": "https://github.com/tdeekens/promster", + "protobuf": "https://github.com/protocolbuffers/protobuf", "quartznet": "https://github.com/quartznet/quartznet", "radix-ui-primitives": "https://github.com/radix-ui/primitives", "reach-ui": "https://github.com/reach/reach-ui", @@ -473,6 +494,7 @@ "https://github.com/ReactTraining/react-router", "https://github.com/remix-run/react-router" ], + "react-spectrum": "https://github.com/adobe/react-spectrum", "reactivestack-cookies": "https://github.com/reactivestack/cookies", "reakit": "https://github.com/reakit/reakit", "redwood": "https://github.com/redwoodjs/redwood", @@ -520,9 +542,11 @@ "strapi": "https://github.com/strapi/strapi", "strum": "https://github.com/Peternator7/strum", "stryker-js": "https://github.com/stryker-mutator/stryker-js", + "stylex-swc": "https://github.com/Dwlad90/stylex-swc-plugin", "surveyjs": "https://github.com/surveyjs/surveyjs", "swashbuckle-aspnetcore": "https://github.com/domaindrivendev/Swashbuckle.AspNetCore", "system.io.abstractions": "https://github.com/System-IO-Abstractions/System.IO.Abstractions/", + "tailwindcss": "https://github.com/tailwindlabs/tailwindcss", "tamagui": "https://github.com/tamagui/tamagui", "tanstack-form": "https://github.com/TanStack/form", "tanstack-query": "https://github.com/TanStack/query", diff --git a/lib/logger/index.spec.ts b/lib/logger/index.spec.ts index 6fc7c67dead27d..236e9a54080e10 100644 --- a/lib/logger/index.spec.ts +++ b/lib/logger/index.spec.ts @@ -17,6 +17,7 @@ import { removeMeta, setContext, setMeta, + withMeta, } from '.'; const initialContext = 'initial_context'; @@ -121,6 +122,43 @@ describe('logger/index', () => { ); expect(bunyanDebugSpy).toHaveBeenCalledTimes(2); }); + + it('withMeta adds and removes metadata correctly', () => { + const logMeta = { foo: 'foo' }; + const tempMeta = { bar: 'bar' }; + + withMeta(tempMeta, () => { + logger.debug(logMeta, ''); + expect(bunyanDebugSpy).toHaveBeenCalledWith( + { logContext: initialContext, ...tempMeta, ...logMeta }, + '', + ); + }); + + logger.debug(logMeta, ''); + expect(bunyanDebugSpy).toHaveBeenCalledWith( + { logContext: initialContext, ...logMeta }, + '', + ); + }); + + it('withMeta handles cleanup when callback throws', () => { + const logMeta = { foo: 'foo' }; + const tempMeta = { bar: 'bar' }; + + expect(() => + withMeta(tempMeta, () => { + logger.debug(logMeta, ''); + throw new Error('test error'); + }), + ).toThrow('test error'); + + logger.debug(logMeta, ''); + expect(bunyanDebugSpy).toHaveBeenCalledWith( + { logContext: initialContext, ...logMeta }, + '', + ); + }); }); it('sets level', () => { diff --git a/lib/logger/index.ts b/lib/logger/index.ts index a120c4a4584b2f..301fdb323da990 100644 --- a/lib/logger/index.ts +++ b/lib/logger/index.ts @@ -126,6 +126,15 @@ export function removeMeta(fields: string[]): void { loggerInternal.removeMeta(fields); } +export function withMeta(obj: Record, cb: () => T): T { + setMeta(obj); + try { + return cb(); + } finally { + removeMeta(Object.keys(obj)); + } +} + export /* istanbul ignore next */ function addStream( stream: bunyan.Stream, ): void { diff --git a/lib/logger/utils.ts b/lib/logger/utils.ts index b85f2c44de22e4..e6278f919e2311 100644 --- a/lib/logger/utils.ts +++ b/lib/logger/utils.ts @@ -317,7 +317,7 @@ export function validateLogLevel( }, ], }); - logger.fatal(`${logLevelToCheck} is not a valid log level. terminating...`); + logger.fatal({ logLevel: logLevelToCheck }, 'Invalid log level'); process.exit(1); } diff --git a/lib/modules/datasource/api.ts b/lib/modules/datasource/api.ts index e7284442c0a36b..58b8a912233f14 100644 --- a/lib/modules/datasource/api.ts +++ b/lib/modules/datasource/api.ts @@ -6,6 +6,7 @@ import { AzurePipelinesTasksDatasource } from './azure-pipelines-tasks'; import { BazelDatasource } from './bazel'; import { BitbucketTagsDatasource } from './bitbucket-tags'; import { BitriseDatasource } from './bitrise'; +import { BuildpacksRegistryDatasource } from './buildpacks-registry'; import { CdnjsDatasource } from './cdnjs'; import { ClojureDatasource } from './clojure'; import { ConanDatasource } from './conan'; @@ -17,6 +18,7 @@ import { DartDatasource } from './dart'; import { DartVersionDatasource } from './dart-version'; import { DebDatasource } from './deb'; import { DenoDatasource } from './deno'; +import { DevboxDatasource } from './devbox'; import { DockerDatasource } from './docker'; import { DotnetVersionDatasource } from './dotnet-version'; import { EndoflifeDateDatasource } from './endoflife-date'; @@ -77,6 +79,7 @@ api.set(AzurePipelinesTasksDatasource.id, new AzurePipelinesTasksDatasource()); api.set(BazelDatasource.id, new BazelDatasource()); api.set(BitbucketTagsDatasource.id, new BitbucketTagsDatasource()); api.set(BitriseDatasource.id, new BitriseDatasource()); +api.set(BuildpacksRegistryDatasource.id, new BuildpacksRegistryDatasource()); api.set(CdnjsDatasource.id, new CdnjsDatasource()); api.set(ClojureDatasource.id, new ClojureDatasource()); api.set(ConanDatasource.id, new ConanDatasource()); @@ -88,6 +91,7 @@ api.set(DartDatasource.id, new DartDatasource()); api.set(DartVersionDatasource.id, new DartVersionDatasource()); api.set(DebDatasource.id, new DebDatasource()); api.set(DenoDatasource.id, new DenoDatasource()); +api.set(DevboxDatasource.id, new DevboxDatasource()); api.set(DockerDatasource.id, new DockerDatasource()); api.set(DotnetVersionDatasource.id, new DotnetVersionDatasource()); api.set(EndoflifeDateDatasource.id, new EndoflifeDateDatasource()); diff --git a/lib/modules/datasource/artifactory/__fixtures__/releases-as-files.html b/lib/modules/datasource/artifactory/__fixtures__/releases-as-files.html index 2bdde583999c96..3d5bf59b799634 100644 --- a/lib/modules/datasource/artifactory/__fixtures__/releases-as-files.html +++ b/lib/modules/datasource/artifactory/__fixtures__/releases-as-files.html @@ -11,9 +11,9 @@

Index

          ..
          1.0.0  21-Jul-2021 20:08    -
-         1.0.1  23-Aug-2021 20:03    -
-         1.0.2  21-Jul-2021 20:09    -
-         1.0.3  06-Feb-2021 09:54    -
+         1.0.1  23-Aug-2021 20:03    12 MB
+         1.0.2  21-Jul-2021 20:09    123.45 GB
+         1.0.3  06-Feb-2021 09:54    9.0 KB
        

Artifactory Port 8080
diff --git a/lib/modules/datasource/artifactory/index.ts b/lib/modules/datasource/artifactory/index.ts index e66ffb4ebaac59..54657508be9b5d 100644 --- a/lib/modules/datasource/artifactory/index.ts +++ b/lib/modules/datasource/artifactory/index.ts @@ -3,6 +3,7 @@ import { cache } from '../../../util/cache/package/decorator'; import { parse } from '../../../util/html'; import { HttpError } from '../../../util/http'; import { regEx } from '../../../util/regex'; +import { asTimestamp } from '../../../util/timestamp'; import { joinUrlParts } from '../../../util/url'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; @@ -72,13 +73,13 @@ export class ArtifactoryDatasource extends Datasource { ? node.innerHTML.slice(0, -1) : node.innerHTML; - const published = ArtifactoryDatasource.parseReleaseTimestamp( - node.nextSibling!.text, // TODO: can be null (#22198) + const releaseTimestamp = asTimestamp( + node.nextSibling?.text?.trimStart()?.split(regEx(/\s{2,}/))?.[0], ); const thisRelease: Release = { version, - releaseTimestamp: published, + releaseTimestamp, }; result.releases.push(thisRelease); @@ -112,8 +113,4 @@ export class ArtifactoryDatasource extends Datasource { return result.releases.length ? result : null; } - - private static parseReleaseTimestamp(rawText: string): string { - return rawText.trim().replace(regEx(/ ?-$/), '') + 'Z'; - } } diff --git a/lib/modules/datasource/aws-machine-image/index.ts b/lib/modules/datasource/aws-machine-image/index.ts index f069ef5a52777e..1c3dce2d985107 100644 --- a/lib/modules/datasource/aws-machine-image/index.ts +++ b/lib/modules/datasource/aws-machine-image/index.ts @@ -2,6 +2,7 @@ import type { Filter, Image } from '@aws-sdk/client-ec2'; import { DescribeImagesCommand, EC2Client } from '@aws-sdk/client-ec2'; import { fromNodeProviderChain } from '@aws-sdk/credential-providers'; import { cache } from '../../../util/cache/package/decorator'; +import { asTimestamp } from '../../../util/timestamp'; import * as amazonMachineImageVersioning from '../../versioning/aws-machine-image'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, ReleaseResult } from '../types'; @@ -147,7 +148,7 @@ export class AwsMachineImageDatasource extends Datasource { releases: [ { version: latestImage.ImageId, - releaseTimestamp: latestImage.CreationDate, + releaseTimestamp: asTimestamp(latestImage.CreationDate), isDeprecated: Date.parse(latestImage.DeprecationTime ?? this.now.toString()) < this.now, diff --git a/lib/modules/datasource/azure-pipelines-tasks/index.ts b/lib/modules/datasource/azure-pipelines-tasks/index.ts index 2605cd91a78845..d7c81d19e960ea 100644 --- a/lib/modules/datasource/azure-pipelines-tasks/index.ts +++ b/lib/modules/datasource/azure-pipelines-tasks/index.ts @@ -93,7 +93,7 @@ export class AzurePipelinesTasksDatasource extends Datasource { key: (url: string) => url, ttlMinutes: 24 * 60, }) - async getTasks = ZodType>( + async getTasks( url: string, opts: HttpOptions, schema: Schema, diff --git a/lib/modules/datasource/bazel/__fixtures__/metadata-no-yanked-versions.json b/lib/modules/datasource/bazel/__fixtures__/metadata-no-yanked-versions.json index 2bbf5b1186fc61..4185ec2d0c792b 100644 --- a/lib/modules/datasource/bazel/__fixtures__/metadata-no-yanked-versions.json +++ b/lib/modules/datasource/bazel/__fixtures__/metadata-no-yanked-versions.json @@ -1,4 +1,5 @@ { + "homepage": "https://github.com/foo/bar", "versions": [ "0.14.8", "0.14.9", diff --git a/lib/modules/datasource/bazel/index.spec.ts b/lib/modules/datasource/bazel/index.spec.ts index f594d09fae2546..356743db17292f 100644 --- a/lib/modules/datasource/bazel/index.spec.ts +++ b/lib/modules/datasource/bazel/index.spec.ts @@ -58,6 +58,7 @@ describe('modules/datasource/bazel/index', () => { { version: '0.15.0' }, { version: '0.16.0' }, ], + sourceUrl: 'https://github.com/foo/bar', }); }); diff --git a/lib/modules/datasource/bazel/index.ts b/lib/modules/datasource/bazel/index.ts index a705a304263eda..277f08f640a35c 100644 --- a/lib/modules/datasource/bazel/index.ts +++ b/lib/modules/datasource/bazel/index.ts @@ -57,6 +57,9 @@ export class BazelDatasource extends Datasource { } return release; }); + if (metadata.homepage) { + result.homepage = metadata.homepage; + } } catch (err) { // istanbul ignore else: not testable with nock if (err instanceof HttpError) { diff --git a/lib/modules/datasource/bazel/schema.ts b/lib/modules/datasource/bazel/schema.ts index 4315d260c4c93a..6fe7245a574b9b 100644 --- a/lib/modules/datasource/bazel/schema.ts +++ b/lib/modules/datasource/bazel/schema.ts @@ -1,6 +1,7 @@ import { z } from 'zod'; export const BazelModuleMetadata = z.object({ + homepage: z.string().optional().nullable(), versions: z.array(z.string()), yanked_versions: z.record(z.string(), z.string()), }); diff --git a/lib/modules/datasource/bitbucket-tags/index.ts b/lib/modules/datasource/bitbucket-tags/index.ts index f8b59d23196bf3..66c39743d1727b 100644 --- a/lib/modules/datasource/bitbucket-tags/index.ts +++ b/lib/modules/datasource/bitbucket-tags/index.ts @@ -1,6 +1,7 @@ import { cache } from '../../../util/cache/package/decorator'; import type { PackageCacheNamespace } from '../../../util/cache/package/types'; import { BitbucketHttp } from '../../../util/http/bitbucket'; +import { asTimestamp } from '../../../util/timestamp'; import { ensureTrailingSlash } from '../../../util/url'; import { RepoInfo } from '../../platform/bitbucket/schema'; import type { PagedResult } from '../../platform/bitbucket/types'; @@ -65,9 +66,12 @@ export class BitbucketTagsDatasource extends Datasource { }: GetReleasesConfig): Promise { const url = `/2.0/repositories/${repo}/refs/tags`; const bitbucketTags = ( - await this.bitbucketHttp.getJson>(url, { - paginate: true, - }) + await this.bitbucketHttp.getJsonUnchecked>( + url, + { + paginate: true, + }, + ) ).body.values; const dependency: ReleaseResult = { @@ -76,7 +80,7 @@ export class BitbucketTagsDatasource extends Datasource { releases: bitbucketTags.map(({ name, target }) => ({ version: name, gitRef: name, - releaseTimestamp: target?.date, + releaseTimestamp: asTimestamp(target?.date), })), }; @@ -96,8 +100,9 @@ export class BitbucketTagsDatasource extends Datasource { ): Promise { const url = `/2.0/repositories/${repo}/refs/tags/${tag}`; - const bitbucketTag = (await this.bitbucketHttp.getJson(url)) - .body; + const bitbucketTag = ( + await this.bitbucketHttp.getJsonUnchecked(url) + ).body; return bitbucketTag.target?.hash ?? null; } @@ -136,7 +141,9 @@ export class BitbucketTagsDatasource extends Datasource { const url = `/2.0/repositories/${repo}/commits/${mainBranch}`; const bitbucketCommits = ( - await this.bitbucketHttp.getJson>(url) + await this.bitbucketHttp.getJsonUnchecked>( + url, + ) ).body; if (bitbucketCommits.values.length === 0) { diff --git a/lib/modules/datasource/bitrise/index.ts b/lib/modules/datasource/bitrise/index.ts index f3b2b66b838e14..5b6465797593d2 100644 --- a/lib/modules/datasource/bitrise/index.ts +++ b/lib/modules/datasource/bitrise/index.ts @@ -6,7 +6,6 @@ import { parseGitUrl } from '../../../util/git/url'; import { GithubHttp } from '../../../util/http/github'; import { fromBase64 } from '../../../util/string'; import { joinUrlParts } from '../../../util/url'; -import { parseSingleYaml } from '../../../util/yaml'; import { GithubContentResponse } from '../../platform/github/schema'; import semver from '../../versioning/semver'; import { Datasource } from '../datasource'; @@ -104,16 +103,14 @@ export class BitriseDatasource extends Datasource { } if (body.encoding !== 'base64') { logger.warn( - { data: body, url: stepUrl }, - `Got unexpected encoding for Bitrise step location '${body.encoding}'`, + { encoding: body.encoding, data: body, url: stepUrl }, + `Got unexpected encoding for Bitrise step location`, ); return null; } const content = fromBase64(body.content); - const { published_at, source_code_url } = parseSingleYaml(content, { - customSchema: BitriseStepFile, - }); + const { published_at, source_code_url } = BitriseStepFile.parse(content); result.releases.push({ version: versionDir.name, diff --git a/lib/modules/datasource/bitrise/schema.ts b/lib/modules/datasource/bitrise/schema.ts index b06b5d3c105cd0..ac29bec00e694b 100644 --- a/lib/modules/datasource/bitrise/schema.ts +++ b/lib/modules/datasource/bitrise/schema.ts @@ -1,6 +1,10 @@ import { z } from 'zod'; +import { Yaml } from '../../../util/schema-utils'; +import { MaybeTimestamp } from '../../../util/timestamp'; -export const BitriseStepFile = z.object({ - published_at: z.string(), - source_code_url: z.string().optional(), -}); +export const BitriseStepFile = Yaml.pipe( + z.object({ + published_at: MaybeTimestamp, + source_code_url: z.string().optional(), + }), +); diff --git a/lib/modules/datasource/buildpacks-registry/index.spec.ts b/lib/modules/datasource/buildpacks-registry/index.spec.ts new file mode 100644 index 00000000000000..5c386d9aadb7c2 --- /dev/null +++ b/lib/modules/datasource/buildpacks-registry/index.spec.ts @@ -0,0 +1,66 @@ +import { getPkgReleases } from '..'; +import * as httpMock from '../../../../test/http-mock'; +import { BuildpacksRegistryDatasource } from '.'; + +const baseUrl = 'https://registry.buildpacks.io/api/v1/buildpacks/'; + +describe('modules/datasource/buildpacks-registry/index', () => { + describe('getReleases', () => { + it('processes real data', async () => { + httpMock + .scope(baseUrl) + .get('/heroku/python') + .reply(200, { + latest: { + version: '0.17.1', + namespace: 'heroku', + name: 'python', + description: "Heroku's buildpack for Python applications.", + homepage: 'https://github.com/heroku/buildpacks-python', + licenses: ['BSD-3-Clause'], + stacks: ['*'], + id: '75946bf8-3f6a-4af0-a757-614bebfdfcd6', + }, + versions: [ + { + version: '0.17.1', + _link: + 'https://registry.buildpacks.io//api/v1/buildpacks/heroku/python/0.17.1', + }, + { + version: '0.17.0', + _link: + 'https://registry.buildpacks.io//api/v1/buildpacks/heroku/python/0.17.0', + }, + ], + }); + const res = await getPkgReleases({ + datasource: BuildpacksRegistryDatasource.id, + packageName: 'heroku/python', + }); + expect(res).toEqual({ + registryUrl: 'https://registry.buildpacks.io', + releases: [{ version: '0.17.0' }, { version: '0.17.1' }], + sourceUrl: 'https://github.com/heroku/buildpacks-python', + }); + }); + + it('returns null on empty result', async () => { + httpMock.scope(baseUrl).get('/heroku/empty').reply(200, {}); + const res = await getPkgReleases({ + datasource: BuildpacksRegistryDatasource.id, + packageName: 'heroku/empty', + }); + expect(res).toBeNull(); + }); + + it('handles not found', async () => { + httpMock.scope(baseUrl).get('/heroku/notexisting').reply(404); + const res = await getPkgReleases({ + datasource: BuildpacksRegistryDatasource.id, + packageName: 'heroku/notexisting', + }); + expect(res).toBeNull(); + }); + }); +}); diff --git a/lib/modules/datasource/buildpacks-registry/index.ts b/lib/modules/datasource/buildpacks-registry/index.ts new file mode 100644 index 00000000000000..bf54f00daf8b35 --- /dev/null +++ b/lib/modules/datasource/buildpacks-registry/index.ts @@ -0,0 +1,72 @@ +import urlJoin from 'url-join'; +import { ZodError } from 'zod'; +import { logger } from '../../../logger'; +import { cache } from '../../../util/cache/package/decorator'; +import { Result } from '../../../util/result'; +import { Datasource } from '../datasource'; +import { ReleasesConfig } from '../schema'; +import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; +import { BuildpacksRegistryResponseSchema } from './schema'; + +export class BuildpacksRegistryDatasource extends Datasource { + static readonly id = 'buildpacks-registry'; + + constructor() { + super(BuildpacksRegistryDatasource.id); + } + + override readonly customRegistrySupport = false; + + override readonly defaultRegistryUrls = ['https://registry.buildpacks.io']; + + override readonly releaseTimestampSupport = true; + override readonly releaseTimestampNote = + 'The release timestamp is determined from the `published_at` field in the results.'; + override readonly sourceUrlSupport = 'release'; + override readonly sourceUrlNote = + 'The source URL is determined from the `source_code_url` field of the release object in the results.'; + + @cache({ + namespace: `datasource-${BuildpacksRegistryDatasource.id}`, + key: ({ registryUrl, packageName }: GetReleasesConfig) => + `${registryUrl}:${packageName}`, + }) + async getReleases(config: GetReleasesConfig): Promise { + const result = Result.parse(config, ReleasesConfig) + .transform(({ packageName, registryUrl }) => { + const url = urlJoin( + registryUrl, + 'api', + 'v1', + 'buildpacks', + packageName, + ); + + return this.http.getJsonSafe(url, BuildpacksRegistryResponseSchema); + }) + .transform(({ versions, latest }): ReleaseResult => { + const releases: Release[] = versions; + + const res: ReleaseResult = { releases }; + + if (latest?.homepage) { + res.homepage = latest.homepage; + } + + return res; + }); + + const { val, err } = await result.unwrap(); + + if (err instanceof ZodError) { + logger.debug({ err }, 'buildpacks: validation error'); + return null; + } + + if (err) { + this.handleGenericErrors(err); + } + + return val; + } +} diff --git a/lib/modules/datasource/buildpacks-registry/schema.spec.ts b/lib/modules/datasource/buildpacks-registry/schema.spec.ts new file mode 100644 index 00000000000000..a46bd3998fe8fe --- /dev/null +++ b/lib/modules/datasource/buildpacks-registry/schema.spec.ts @@ -0,0 +1,43 @@ +import { BuildpacksRegistryResponseSchema } from './schema'; + +describe('modules/datasource/buildpacks-registry/schema', () => { + it('parses buildpack-registry schema', () => { + const response = { + latest: { + version: '0.17.1', + namespace: 'heroku', + name: 'python', + description: "Heroku's buildpack for Python applications.", + homepage: 'https://github.com/heroku/buildpacks-python', + licenses: ['BSD-3-Clause'], + stacks: ['*'], + id: '75946bf8-3f6a-4af0-a757-614bebfdfcd6', + }, + versions: [ + { + version: '0.2.0', + _link: + 'https://registry.buildpacks.io//api/v1/buildpacks/heroku/python/0.2.0', + }, + { + version: '0.1.0', + _link: + 'https://registry.buildpacks.io//api/v1/buildpacks/heroku/python/0.1.0', + }, + ], + }; + expect(BuildpacksRegistryResponseSchema.parse(response)).toMatchObject({ + latest: { + homepage: 'https://github.com/heroku/buildpacks-python', + }, + versions: [ + { + version: '0.2.0', + }, + { + version: '0.1.0', + }, + ], + }); + }); +}); diff --git a/lib/modules/datasource/buildpacks-registry/schema.ts b/lib/modules/datasource/buildpacks-registry/schema.ts new file mode 100644 index 00000000000000..6bc544196380ea --- /dev/null +++ b/lib/modules/datasource/buildpacks-registry/schema.ts @@ -0,0 +1,17 @@ +import { z } from 'zod'; + +/** + * Response from registry.buildpacks.io + */ +export const BuildpacksRegistryResponseSchema = z.object({ + latest: z + .object({ + homepage: z.string().optional(), + }) + .optional(), + versions: z + .object({ + version: z.string(), + }) + .array(), +}); diff --git a/lib/modules/datasource/conan/common.ts b/lib/modules/datasource/conan/common.ts index bba6a0a0213af2..fce9a20255a83a 100644 --- a/lib/modules/datasource/conan/common.ts +++ b/lib/modules/datasource/conan/common.ts @@ -6,7 +6,7 @@ export const defaultRegistryUrl = 'https://center2.conan.io/'; export const datasource = 'conan'; export const conanDatasourceRegex = regEx( - /(?[a-zA-Z\-_0-9]+)\/(?[^@/\n]+)(?@\S+\/\S+)/gim, + /^(?[a-zA-Z\-_0-9]+)\/(?[^@/\n]+)(?@\S+\/\S+)$/im, ); export function getConanPackage(packageName: string): ConanPackage { diff --git a/lib/modules/datasource/conan/index.spec.ts b/lib/modules/datasource/conan/index.spec.ts index f12ea65847e878..56c7faa5c1db71 100644 --- a/lib/modules/datasource/conan/index.spec.ts +++ b/lib/modules/datasource/conan/index.spec.ts @@ -360,6 +360,7 @@ describe('modules/datasource/conan/index', () => { version: '1.1.1', }, ], + sourceUrl: 'https://fake.conan.url.com', }); }); diff --git a/lib/modules/datasource/conan/index.ts b/lib/modules/datasource/conan/index.ts index 8ecb40124792bb..079600bd4df9e8 100644 --- a/lib/modules/datasource/conan/index.ts +++ b/lib/modules/datasource/conan/index.ts @@ -3,7 +3,6 @@ import { logger } from '../../../logger'; import { cache } from '../../../util/cache/package/decorator'; import { GithubHttp } from '../../../util/http/github'; import { ensureTrailingSlash, joinUrlParts } from '../../../util/url'; -import { parseSingleYaml } from '../../../util/yaml'; import * as allVersioning from '../../versioning'; import { Datasource } from '../datasource'; import type { @@ -13,19 +12,14 @@ import type { ReleaseResult, } from '../types'; import { isArtifactoryServer } from '../util'; +import { datasource, defaultRegistryUrl, getConanPackage } from './common'; import { - conanDatasourceRegex, - datasource, - defaultRegistryUrl, - getConanPackage, -} from './common'; -import type { + ConanCenterReleases, ConanJSON, + ConanLatestRevision, ConanProperties, ConanRevisionJSON, - ConanRevisionsJSON, - ConanYAML, -} from './types'; +} from './schema'; export class ConanDatasource extends Datasource { static readonly id = datasource; @@ -59,16 +53,12 @@ export class ConanDatasource extends Datasource { return null; } const url = `https://api.github.com/repos/conan-io/conan-center-index/contents/recipes/${conanName}/config.yml`; - const res = await this.githubHttp.get(url, { - headers: { accept: 'application/vnd.github.v3.raw' }, - }); - // TODO: use schema (#9610) - const doc = parseSingleYaml(res.body); - return { - releases: Object.keys(doc?.versions ?? {}).map((version) => ({ - version, - })), - }; + const { body: result } = await this.githubHttp.getYaml( + url, + { headers: { accept: 'application/vnd.github.v3.raw' } }, + ConanCenterReleases, + ); + return result; } @cache({ @@ -94,10 +84,11 @@ export class ConanDatasource extends Datasource { conanPackage.userAndChannel, '/revisions', ); - const revisionRep = - await this.http.getJson(revisionLookUp); - const revisions = revisionRep?.body.revisions; - return revisions?.[0].revision ?? null; + const { body: digest } = await this.http.getJson( + revisionLookUp, + ConanLatestRevision, + ); + return digest; } @cache({ @@ -135,25 +126,16 @@ export class ConanDatasource extends Datasource { ); try { - const rep = await this.http.getJson(lookupUrl); - const versions = rep?.body; - if (versions) { + const rep = await this.http.getJsonUnchecked(lookupUrl); + const conanJson = ConanJSON.parse(rep.body); + if (conanJson) { logger.trace({ lookupUrl }, 'Got conan api result'); const dep: ReleaseResult = { releases: [] }; - for (const resultString of Object.values(versions.results ?? {})) { - conanDatasourceRegex.lastIndex = 0; - const fromMatch = conanDatasourceRegex.exec(resultString); - if (fromMatch?.groups?.version && fromMatch?.groups?.userChannel) { - const version = fromMatch.groups.version; - if (fromMatch.groups.userChannel === userAndChannel) { - const result: Release = { - version, - }; - dep.releases.push(result); - } - } - } + const conanJsonReleases: Release[] = conanJson + .filter(({ userChannel }) => userChannel === userAndChannel) + .map(({ version }) => ({ version })); + dep.releases.push(...conanJsonReleases); try { if (isArtifactoryServer(rep)) { @@ -182,25 +164,22 @@ export class ConanDatasource extends Datasource { url, `v2/conans/${conanPackage.conanName}/${latestVersion}/${conanPackage.userAndChannel}/latest`, ); - const revResp = - await this.http.getJson(latestRevisionUrl); - const packageRev = revResp.body.revision; + const { + body: { revision: packageRev }, + } = await this.http.getJson(latestRevisionUrl, ConanRevisionJSON); const [user, channel] = conanPackage.userAndChannel.split('/'); const packageUrl = joinUrlParts( `${groups.host}/artifactory/api/storage/${groups.repo}`, `${user}/${conanPackage.conanName}/${latestVersion}/${channel}/${packageRev}/export/conanfile.py?properties=conan.package.url`, ); - const packageUrlResp = - await this.http.getJson(packageUrl); - - if ( - packageUrlResp.body.properties && - 'conan.package.url' in packageUrlResp.body.properties - ) { - const conanPackageUrl = - packageUrlResp.body.properties['conan.package.url'][0]; - dep.sourceUrl = conanPackageUrl; + const { body: conanProperties } = await this.http.getJson( + packageUrl, + ConanProperties, + ); + const { sourceUrl } = conanProperties; + if (sourceUrl) { + dep.sourceUrl = sourceUrl; } } } catch (err) { diff --git a/lib/modules/datasource/conan/schema.ts b/lib/modules/datasource/conan/schema.ts new file mode 100644 index 00000000000000..c13ec77b8c845f --- /dev/null +++ b/lib/modules/datasource/conan/schema.ts @@ -0,0 +1,65 @@ +import { z } from 'zod'; +import { LooseArray } from '../../../util/schema-utils'; +import type { ReleaseResult } from '../types'; +import { conanDatasourceRegex } from './common'; + +export const ConanCenterReleases = z + .object({ + versions: z.record(z.string(), z.unknown()), + }) + .transform( + ({ versions }): ReleaseResult => ({ + releases: Object.keys(versions).map((version) => ({ version })), + }), + ) + .nullable() + .catch(null); + +export const ConanJSON = z + .object({ + results: z + .string() + .array() + .transform((array) => + array.map((val) => val.match(conanDatasourceRegex)?.groups), + ) + .pipe( + LooseArray( + z.object({ + name: z.string(), + version: z.string(), + userChannel: z.string(), + }), + ), + ), + }) + .transform(({ results }) => results) + .nullable() + .catch(null); + +export const ConanRevisionJSON = z.object({ + revision: z.string(), + time: z.string(), +}); + +export const ConanLatestRevision = z + .object({ revisions: z.unknown().array() }) + .transform(({ revisions }) => revisions[0]) + .pipe(ConanRevisionJSON) + .transform(({ revision }) => revision) + .nullable() + .catch(null); + +export const ConanProperties = z + .object({ + properties: z.object({ + 'conan.package.url': z.union([ + z.string().transform((url) => [url]), + z.string().array(), + ]), + }), + }) + .transform(({ properties }) => { + const sourceUrl = properties['conan.package.url'][0]; + return { sourceUrl }; + }); diff --git a/lib/modules/datasource/conan/types.ts b/lib/modules/datasource/conan/types.ts index bb65481f5015f8..18dda2e72854d1 100644 --- a/lib/modules/datasource/conan/types.ts +++ b/lib/modules/datasource/conan/types.ts @@ -1,35 +1,4 @@ -export interface ConanJSON { - results?: Record; -} - -export interface ConanRevisionJSON { - revision: string; - time: string; -} - -export interface ConanRevisionsJSON { - revisions?: Record; -} - -export interface ConanYAML { - versions?: Record; -} - export interface ConanPackage { conanName: string; userAndChannel: string; } - -export interface ConanRecipeProperties { - 'conan.package.channel': string[]; - 'conan.package.license': string[]; - 'conan.package.name': string[]; - 'conan.package.url': string[]; - 'conan.package.user': string[]; - 'conan.package.version': string[]; -} - -export interface ConanProperties { - properties: ConanRecipeProperties; - uri: string; -} diff --git a/lib/modules/datasource/conda/index.ts b/lib/modules/datasource/conda/index.ts index 980eb8de9be103..6985d4d4cde7db 100644 --- a/lib/modules/datasource/conda/index.ts +++ b/lib/modules/datasource/conda/index.ts @@ -52,7 +52,7 @@ export class CondaDatasource extends Datasource { let response: { body: CondaPackage }; try { - response = await this.http.getJson(url); + response = await this.http.getJsonUnchecked(url); result.homepage = response.body.html_url; result.sourceUrl = response.body.dev_url; diff --git a/lib/modules/datasource/cpan/schema.ts b/lib/modules/datasource/cpan/schema.ts index 8e997143ba3849..e44ce810626f50 100644 --- a/lib/modules/datasource/cpan/schema.ts +++ b/lib/modules/datasource/cpan/schema.ts @@ -1,5 +1,6 @@ import { z } from 'zod'; import { LooseArray } from '../../../util/schema-utils'; +import { MaybeTimestamp } from '../../../util/timestamp'; import type { CpanRelease } from './types'; /** @@ -14,7 +15,7 @@ const MetaCpanApiFileSchema = z }), ), distribution: z.string(), - date: z.string(), + date: MaybeTimestamp, deprecated: z.boolean(), maturity: z.string(), status: z.union([ diff --git a/lib/modules/datasource/crate/index.spec.ts b/lib/modules/datasource/crate/index.spec.ts index 765bba4d61ecdb..f5138c14b37169 100644 --- a/lib/modules/datasource/crate/index.spec.ts +++ b/lib/modules/datasource/crate/index.spec.ts @@ -427,7 +427,7 @@ describe('modules/datasource/crate/index', () => { expect(res).toEqual({ version: '4.5.17', - releaseTimestamp: '2024-09-04T19:16:41.355243+00:00', + releaseTimestamp: '2024-09-04T19:16:41.355Z', }); }); }); diff --git a/lib/modules/datasource/crate/index.ts b/lib/modules/datasource/crate/index.ts index 29eb0b84985f23..7e0be2656c71eb 100644 --- a/lib/modules/datasource/crate/index.ts +++ b/lib/modules/datasource/crate/index.ts @@ -156,7 +156,7 @@ export class CrateDatasource extends Datasource { try { type Response = { crate: CrateMetadata }; - const response = await this.http.getJson(crateUrl); + const response = await this.http.getJsonUnchecked(crateUrl); return response.body.crate; } catch (err) { logger.warn( diff --git a/lib/modules/datasource/crate/schema.ts b/lib/modules/datasource/crate/schema.ts index 9d799be31b42cc..af908f22a93262 100644 --- a/lib/modules/datasource/crate/schema.ts +++ b/lib/modules/datasource/crate/schema.ts @@ -1,9 +1,10 @@ import { z } from 'zod'; +import { MaybeTimestamp } from '../../../util/timestamp'; export const ReleaseTimestampSchema = z .object({ version: z.object({ - created_at: z.string(), + created_at: MaybeTimestamp, }), }) .transform(({ version: { created_at } }) => created_at) diff --git a/lib/modules/datasource/custom/formats/json.ts b/lib/modules/datasource/custom/formats/json.ts index ead333419bbd91..63b7fb70ad4223 100644 --- a/lib/modules/datasource/custom/formats/json.ts +++ b/lib/modules/datasource/custom/formats/json.ts @@ -4,7 +4,7 @@ import type { CustomDatasourceFetcher } from './types'; export class JSONFetcher implements CustomDatasourceFetcher { async fetch(http: Http, registryURL: string): Promise { - const response = await http.getJson(registryURL); + const response = await http.getJsonUnchecked(registryURL); return response.body; } diff --git a/lib/modules/datasource/custom/schema.ts b/lib/modules/datasource/custom/schema.ts index 50c19b7ed948b1..c45628064f4a94 100644 --- a/lib/modules/datasource/custom/schema.ts +++ b/lib/modules/datasource/custom/schema.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { MaybeTimestamp } from '../../../util/timestamp'; export const ReleaseResultZodSchema = z.object({ releases: z.array( @@ -6,7 +7,7 @@ export const ReleaseResultZodSchema = z.object({ .object({ version: z.string(), isDeprecated: z.boolean().optional(), - releaseTimestamp: z.string().optional(), + releaseTimestamp: MaybeTimestamp, sourceUrl: z.string().optional(), sourceDirectory: z.string().optional(), changelogUrl: z.string().optional(), diff --git a/lib/modules/datasource/dart-version/index.ts b/lib/modules/datasource/dart-version/index.ts index 23f95ccab89124..06b5440f8fbb7d 100644 --- a/lib/modules/datasource/dart-version/index.ts +++ b/lib/modules/datasource/dart-version/index.ts @@ -41,7 +41,7 @@ export class DartVersionDatasource extends Datasource { try { for (const channel of this.channels) { const resp = ( - await this.http.getJson( + await this.http.getJsonUnchecked( `${registryUrl}/storage/v1/b/dart-archive/o?delimiter=%2F&prefix=channels%2F${channel}%2Frelease%2F&alt=json`, ) ).body; diff --git a/lib/modules/datasource/dart/index.ts b/lib/modules/datasource/dart/index.ts index 88e549cfeb8903..5cae12d29a50df 100644 --- a/lib/modules/datasource/dart/index.ts +++ b/lib/modules/datasource/dart/index.ts @@ -1,4 +1,5 @@ import type { HttpResponse } from '../../../util/http/types'; +import { asTimestamp } from '../../../util/timestamp'; import { ensureTrailingSlash } from '../../../util/url'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, ReleaseResult } from '../types'; @@ -37,7 +38,7 @@ export class DartDatasource extends Datasource { let raw: HttpResponse | null = null; try { - raw = await this.http.getJson(pkgUrl); + raw = await this.http.getJsonUnchecked(pkgUrl); } catch (err) { this.handleGenericErrors(err); } @@ -49,7 +50,7 @@ export class DartDatasource extends Datasource { ?.filter(({ retracted }) => !retracted) ?.map(({ version, published }) => ({ version, - releaseTimestamp: published, + releaseTimestamp: asTimestamp(published), })); if (releases && latest) { result = { releases }; diff --git a/lib/modules/datasource/deb/index.ts b/lib/modules/datasource/deb/index.ts index ad29d107df4977..192bc6fdb25910 100644 --- a/lib/modules/datasource/deb/index.ts +++ b/lib/modules/datasource/deb/index.ts @@ -90,13 +90,14 @@ export class DebDatasource extends Datasource { await extract(compressedFile, compression, extractedFile); lastTimestamp = await getFileCreationTime(extractedFile); } catch (error) { - logger.error( + logger.warn( { + compressedFile, componentUrl, compression, error: error.message, }, - `Failed to extract package file from ${compressedFile}`, + 'Failed to extract package file from compressed file', ); } finally { await fs.rmCache(compressedFile); @@ -213,7 +214,8 @@ export class DebDatasource extends Datasource { return response.statusCode !== 304; } catch (error) { logger.warn( - `Could not determine if ${packageUrl} is modified since ${lastDownloadTimestamp.toUTCString()}: ${error.message}`, + { packageUrl, lastDownloadTimestamp, errorMessage: error.message }, + 'Could not determine if package file is modified since last download', ); return true; // Assume it needs to be downloaded if check fails } diff --git a/lib/modules/datasource/deno/index.spec.ts b/lib/modules/datasource/deno/index.spec.ts index a720572b7cf566..9554fc28aa279d 100644 --- a/lib/modules/datasource/deno/index.spec.ts +++ b/lib/modules/datasource/deno/index.spec.ts @@ -66,8 +66,9 @@ describe('modules/datasource/deno/index', () => { expect(logger.logger.warn).toHaveBeenCalledWith( expect.objectContaining({ err: expect.any(ZodError), + version: '0.161.0', }), - `Deno: failed to get version details for 0.161.0`, + 'Deno: failed to get version details', ); }); diff --git a/lib/modules/datasource/deno/index.ts b/lib/modules/datasource/deno/index.ts index e9ca1cca0527e2..c2178451eba9dd 100644 --- a/lib/modules/datasource/deno/index.ts +++ b/lib/modules/datasource/deno/index.ts @@ -102,8 +102,8 @@ export class DenoDatasource extends Datasource { url, DenoAPIModuleVersionResponse.catch(({ error: err }) => { logger.warn( - { err }, - `Deno: failed to get version details for ${version}`, + { err, version }, + 'Deno: failed to get version details', ); return { version }; }), diff --git a/lib/modules/datasource/deno/schema.ts b/lib/modules/datasource/deno/schema.ts index 2587874425d9d8..a72b7d29669d71 100644 --- a/lib/modules/datasource/deno/schema.ts +++ b/lib/modules/datasource/deno/schema.ts @@ -1,6 +1,7 @@ import { z } from 'zod'; import { getSourceUrl as getGithubSourceUrl } from '../../../util/github/url'; import { LooseArray } from '../../../util/schema-utils'; +import { MaybeTimestamp } from '../../../util/timestamp'; import type { Release } from '../types'; export const DenoApiTag = z.object({ @@ -31,7 +32,7 @@ export const DenoAPIUploadOptions = z.object({ export const DenoAPIModuleVersionResponse = z .object({ upload_options: DenoAPIUploadOptions, - uploaded_at: z.string(), + uploaded_at: MaybeTimestamp, version: z.string(), }) .transform( @@ -41,6 +42,11 @@ export const DenoAPIModuleVersionResponse = z if (type === 'github') { sourceUrl = getGithubSourceUrl(repository); } - return { version, gitRef, releaseTimestamp, sourceUrl }; + return { + version, + gitRef, + releaseTimestamp, + sourceUrl, + }; }, ); diff --git a/lib/modules/datasource/devbox/common.ts b/lib/modules/datasource/devbox/common.ts new file mode 100644 index 00000000000000..9149c754171e9a --- /dev/null +++ b/lib/modules/datasource/devbox/common.ts @@ -0,0 +1,3 @@ +export const defaultRegistryUrl = 'https://search.devbox.sh/v2/'; + +export const datasource = 'devbox'; diff --git a/lib/modules/datasource/devbox/index.spec.ts b/lib/modules/datasource/devbox/index.spec.ts new file mode 100644 index 00000000000000..5b525a34d9c0b8 --- /dev/null +++ b/lib/modules/datasource/devbox/index.spec.ts @@ -0,0 +1,159 @@ +import { getPkgReleases } from '..'; +import * as httpMock from '../../../../test/http-mock'; +import { EXTERNAL_HOST_ERROR } from '../../../constants/error-messages'; +import { datasource, defaultRegistryUrl } from './common'; + +const packageName = 'nodejs'; + +function getPath(packageName: string): string { + return `/pkg?name=${encodeURIComponent(packageName)}`; +} + +const sampleReleases = [ + { + version: '22.2.0', + last_updated: '2024-05-22T06:18:38Z', + }, + { + version: '22.0.0', + last_updated: '2024-05-12T16:19:40Z', + }, + { + version: '21.7.3', + last_updated: '2024-04-19T21:36:04Z', + }, +]; + +describe('modules/datasource/devbox/index', () => { + describe('getReleases', () => { + it('throws for error', async () => { + httpMock + .scope(defaultRegistryUrl) + .get(getPath(packageName)) + .replyWithError('error'); + await expect( + getPkgReleases({ + datasource, + packageName, + }), + ).rejects.toThrow(EXTERNAL_HOST_ERROR); + }); + }); + + it('returns null for 404', async () => { + httpMock.scope(defaultRegistryUrl).get(getPath(packageName)).reply(404); + expect( + await getPkgReleases({ + datasource, + packageName, + }), + ).toBeNull(); + }); + + it('returns null for empty result', async () => { + httpMock.scope(defaultRegistryUrl).get(getPath(packageName)).reply(200, {}); + expect( + await getPkgReleases({ + datasource, + packageName, + }), + ).toBeNull(); + }); + + it('returns null for empty 200 OK', async () => { + httpMock + .scope(defaultRegistryUrl) + .get(getPath(packageName)) + .reply(200, { versions: [] }); + expect( + await getPkgReleases({ + datasource, + packageName, + }), + ).toBeNull(); + }); + + it('throws for 5xx', async () => { + httpMock.scope(defaultRegistryUrl).get(getPath(packageName)).reply(502); + await expect( + getPkgReleases({ + datasource, + packageName, + }), + ).rejects.toThrow(EXTERNAL_HOST_ERROR); + }); + + it('processes real data', async () => { + httpMock.scope(defaultRegistryUrl).get(getPath(packageName)).reply(200, { + name: 'nodejs', + summary: 'Event-driven I/O framework for the V8 JavaScript engine', + homepage_url: 'https://nodejs.org', + license: 'MIT', + releases: sampleReleases, + }); + const res = await getPkgReleases({ + datasource, + packageName, + }); + expect(res).toEqual({ + homepage: 'https://nodejs.org', + registryUrl: 'https://search.devbox.sh/v2', + releases: [ + { + version: '21.7.3', + releaseTimestamp: '2024-04-19T21:36:04.000Z', + }, + { + version: '22.0.0', + releaseTimestamp: '2024-05-12T16:19:40.000Z', + }, + { + version: '22.2.0', + releaseTimestamp: '2024-05-22T06:18:38.000Z', + }, + ], + }); + }); + + it('processes empty data', async () => { + httpMock.scope(defaultRegistryUrl).get(getPath(packageName)).reply(200, { + name: 'nodejs', + summary: 'Event-driven I/O framework for the V8 JavaScript engine', + homepage_url: 'https://nodejs.org', + license: 'MIT', + releases: [], + }); + const res = await getPkgReleases({ + datasource, + packageName, + }); + expect(res).toBeNull(); + }); + + it('returns null when no body is returned', async () => { + httpMock + .scope(defaultRegistryUrl) + .get(getPath(packageName)) + .reply(200, undefined); + const res = await getPkgReleases({ + datasource, + packageName, + }); + expect(res).toBeNull(); + }); + + it('falls back to a default homepage_url', async () => { + httpMock.scope(defaultRegistryUrl).get(getPath(packageName)).reply(200, { + name: 'nodejs', + summary: 'Event-driven I/O framework for the V8 JavaScript engine', + homepage_url: undefined, + license: 'MIT', + releases: sampleReleases, + }); + const res = await getPkgReleases({ + datasource, + packageName, + }); + expect(res?.homepage).toBeUndefined(); + }); +}); diff --git a/lib/modules/datasource/devbox/index.ts b/lib/modules/datasource/devbox/index.ts new file mode 100644 index 00000000000000..be89dced54ee8f --- /dev/null +++ b/lib/modules/datasource/devbox/index.ts @@ -0,0 +1,57 @@ +import { logger } from '../../../logger'; +import { ExternalHostError } from '../../../types/errors/external-host-error'; +import { HttpError } from '../../../util/http'; +import { joinUrlParts } from '../../../util/url'; +import * as devboxVersioning from '../../versioning/devbox'; +import { Datasource } from '../datasource'; +import type { GetReleasesConfig, ReleaseResult } from '../types'; +import { datasource, defaultRegistryUrl } from './common'; +import { DevboxResponse } from './schema'; + +export class DevboxDatasource extends Datasource { + static readonly id = datasource; + + constructor() { + super(datasource); + } + + override readonly customRegistrySupport = true; + override readonly releaseTimestampSupport = true; + + override readonly registryStrategy = 'first'; + + override readonly defaultVersioning = devboxVersioning.id; + + override readonly defaultRegistryUrls = [defaultRegistryUrl]; + + async getReleases({ + registryUrl, + packageName, + }: GetReleasesConfig): Promise { + const res: ReleaseResult = { + releases: [], + }; + + logger.trace({ registryUrl, packageName }, 'fetching devbox release'); + + const devboxPkgUrl = joinUrlParts( + registryUrl!, + `/pkg?name=${encodeURIComponent(packageName)}`, + ); + + try { + const response = await this.http.getJson(devboxPkgUrl, DevboxResponse); + res.releases = response.body.releases; + res.homepage = response.body.homepage; + } catch (err) { + // istanbul ignore else: not testable with nock + if (err instanceof HttpError) { + if (err.response?.statusCode !== 404) { + throw new ExternalHostError(err); + } + } + this.handleGenericErrors(err); + } + return res.releases.length ? res : null; + } +} diff --git a/lib/modules/datasource/devbox/schema.ts b/lib/modules/datasource/devbox/schema.ts new file mode 100644 index 00000000000000..d4c3fac44b6208 --- /dev/null +++ b/lib/modules/datasource/devbox/schema.ts @@ -0,0 +1,24 @@ +import { z } from 'zod'; +import { MaybeTimestamp } from '../../../util/timestamp'; + +export const DevboxRelease = z.object({ + version: z.string(), + last_updated: MaybeTimestamp, +}); + +export const DevboxResponse = z + .object({ + name: z.string(), + summary: z.string().optional(), + homepage_url: z.string().optional(), + license: z.string().optional(), + releases: DevboxRelease.array(), + }) + .transform((response) => ({ + name: response.name, + homepage: response.homepage_url, + releases: response.releases.map((release) => ({ + version: release.version, + releaseTimestamp: release.last_updated, + })), + })); diff --git a/lib/modules/datasource/docker/common.ts b/lib/modules/datasource/docker/common.ts index 1ccc12c66a648d..b838b25d8898e5 100644 --- a/lib/modules/datasource/docker/common.ts +++ b/lib/modules/datasource/docker/common.ts @@ -63,7 +63,7 @@ export async function getAuthHeaders( ? await http.get(apiCheckUrl, options) : // use json request, as this will be cached for tags, so it returns json // TODO: add cache test - await http.getJson(apiCheckUrl, options); + await http.getJsonUnchecked(apiCheckUrl, options); if (apiCheckResponse.statusCode === 200) { logger.debug(`No registry auth required for ${apiCheckUrl}`); @@ -193,7 +193,7 @@ export async function getAuthHeaders( ); opts.noAuth = true; const authResponse = ( - await http.getJson<{ token?: string; access_token?: string }>( + await http.getJsonUnchecked<{ token?: string; access_token?: string }>( authUrl.href, opts, ) diff --git a/lib/modules/datasource/docker/index.ts b/lib/modules/datasource/docker/index.ts index 3e4339d0fa2b25..87c59c23441777 100644 --- a/lib/modules/datasource/docker/index.ts +++ b/lib/modules/datasource/docker/index.ts @@ -10,6 +10,7 @@ import { hasKey } from '../../../util/object'; import { regEx } from '../../../util/regex'; import { type AsyncResult, Result } from '../../../util/result'; import { isDockerDigest } from '../../../util/string-match'; +import { asTimestamp } from '../../../util/timestamp'; import { ensurePathPrefix, joinUrlParts, @@ -629,7 +630,7 @@ export class DockerDatasource extends Datasource { // typescript issue :-/ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - const res = (await this.http.getJson( + const res = (await this.http.getJsonUnchecked( url, )) as HttpResponse; const pageTags = res.body.tags.map((tag) => tag.name); @@ -671,7 +672,7 @@ export class DockerDatasource extends Datasource { do { let res: HttpResponse<{ tags: string[] }>; try { - res = await this.http.getJson<{ tags: string[] }>(url, { + res = await this.http.getJsonUnchecked<{ tags: string[] }>(url, { headers, noAuth: true, }); @@ -1001,13 +1002,10 @@ export class DockerDatasource extends Datasource { const items = cache.getItems(); return items.map( - ({ - name: version, - tag_last_pushed: releaseTimestamp, - digest: newDigest, - }) => { + ({ name: version, tag_last_pushed, digest: newDigest }) => { const release: Release = { version }; + const releaseTimestamp = asTimestamp(tag_last_pushed); if (releaseTimestamp) { release.releaseTimestamp = releaseTimestamp; } diff --git a/lib/modules/datasource/dotnet-version/schema.ts b/lib/modules/datasource/dotnet-version/schema.ts index ce940ac245319a..8660a322b5f067 100644 --- a/lib/modules/datasource/dotnet-version/schema.ts +++ b/lib/modules/datasource/dotnet-version/schema.ts @@ -1,5 +1,6 @@ import { z } from 'zod'; import { LooseArray } from '../../../util/schema-utils'; +import { MaybeTimestamp } from '../../../util/timestamp'; import type { Release } from '../types'; export const ReleasesIndex = z @@ -15,7 +16,7 @@ export const ReleasesIndex = z .transform(({ 'releases-index': releasesIndex }) => releasesIndex); const ReleaseBase = z.object({ - 'release-date': z.string(), + 'release-date': MaybeTimestamp, 'release-notes': z.string(), }); const ReleaseDetails = z.object({ diff --git a/lib/modules/datasource/endoflife-date/schema.ts b/lib/modules/datasource/endoflife-date/schema.ts index 4f184a86fc9d72..aa4e83cd004035 100644 --- a/lib/modules/datasource/endoflife-date/schema.ts +++ b/lib/modules/datasource/endoflife-date/schema.ts @@ -1,6 +1,7 @@ import { DateTime } from 'luxon'; import { z } from 'zod'; import { UtcDate } from '../../../util/schema-utils'; +import { MaybeTimestamp } from '../../../util/timestamp'; import type { Release } from '../types'; const ExpireableField = z.union([ @@ -15,7 +16,7 @@ export const EndoflifeDateVersions = z .object({ cycle: z.string(), latest: z.optional(z.string()), - releaseDate: z.optional(z.string()), + releaseDate: MaybeTimestamp, eol: z.optional(ExpireableField), discontinued: z.optional(ExpireableField), }) diff --git a/lib/modules/datasource/flutter-version/index.ts b/lib/modules/datasource/flutter-version/index.ts index 46dd2ce47e8d44..8fd364877c8d22 100644 --- a/lib/modules/datasource/flutter-version/index.ts +++ b/lib/modules/datasource/flutter-version/index.ts @@ -1,4 +1,5 @@ import { regEx } from '../../../util/regex'; +import { asTimestamp } from '../../../util/timestamp'; import { id as semverId } from '../../versioning/semver'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, ReleaseResult } from '../types'; @@ -43,7 +44,7 @@ export class FlutterVersionDatasource extends Datasource { }; try { const resp = ( - await this.http.getJson( + await this.http.getJsonUnchecked( `${registryUrl}/flutter_infra_release/releases/releases_linux.json`, ) ).body; @@ -58,7 +59,7 @@ export class FlutterVersionDatasource extends Datasource { }) .map(({ version, release_date, channel }) => ({ version, - releaseTimestamp: release_date, + releaseTimestamp: asTimestamp(release_date), isStable: channel === 'stable', })); return result.releases.length ? result : null; diff --git a/lib/modules/datasource/galaxy-collection/index.ts b/lib/modules/datasource/galaxy-collection/index.ts index ffd91268ea96d8..bfafa69db2b63d 100644 --- a/lib/modules/datasource/galaxy-collection/index.ts +++ b/lib/modules/datasource/galaxy-collection/index.ts @@ -65,8 +65,8 @@ export class GalaxyCollectionDatasource extends Datasource { .getJsonSafe(baseUrl, GalaxyV3) .onError((err) => { logger.warn( - { datasource: this.id, packageName, err }, - `Error fetching ${baseUrl}`, + { url: baseUrl, datasource: this.id, packageName, err }, + 'Error fetching from url', ); }) .unwrap(); @@ -80,8 +80,8 @@ export class GalaxyCollectionDatasource extends Datasource { .getJsonSafe(versionsUrl, GalaxyV3Versions) .onError((err) => { logger.warn( - { datasource: this.id, packageName, err }, - `Error fetching ${versionsUrl}`, + { url: versionsUrl, datasource: this.id, packageName, err }, + 'Error fetching from url', ); }) .unwrap(); @@ -136,8 +136,8 @@ export class GalaxyCollectionDatasource extends Datasource { .getJsonSafe(detailedVersionUrl, GalaxyV3DetailedVersion) .onError((err) => { logger.warn( - { datasource: this.id, packageName, err }, - `Error fetching ${versionsUrl}`, + { url: versionsUrl, datasource: this.id, packageName, err }, + 'Error fetching from url', ); }) .unwrap(); diff --git a/lib/modules/datasource/galaxy-collection/schema.ts b/lib/modules/datasource/galaxy-collection/schema.ts index 06be4898c52e99..0842fc9ccbec59 100644 --- a/lib/modules/datasource/galaxy-collection/schema.ts +++ b/lib/modules/datasource/galaxy-collection/schema.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { MaybeTimestamp } from '../../../util/timestamp'; export type GalaxyV3 = z.infer; export const GalaxyV3 = z.object({ @@ -14,7 +15,7 @@ export const GalaxyV3Versions = z data: z.array( z.object({ version: z.string(), - created_at: z.string().datetime(), + created_at: MaybeTimestamp, }), ), }) diff --git a/lib/modules/datasource/galaxy/index.ts b/lib/modules/datasource/galaxy/index.ts index a0a01011c53241..075653dddf5beb 100644 --- a/lib/modules/datasource/galaxy/index.ts +++ b/lib/modules/datasource/galaxy/index.ts @@ -57,15 +57,14 @@ export class GalaxyDatasource extends Datasource { if (!body.results.length) { logger.warn( { dependency: packageName, userName }, - `Multiple results from galaxy for ${packageName}, none match`, + `No matching result from galaxy for package`, ); return null; } } if (body.results.length === 0) { - logger.info( - { dependency: packageName }, - `Received no results from ${galaxyAPIUrl}`, + logger.debug( + `Received no results for ${packageName} from ${galaxyAPIUrl} `, ); return null; } @@ -83,18 +82,13 @@ export class GalaxyDatasource extends Datasource { result.sourceUrl = `https://github.com/${user}/${repo}`; } - result.releases = versions.map( - (version: { name: string; created?: string }) => { - const release: Release = { - version: version.name, - }; - - if (is.nonEmptyString(version.created)) { - release.releaseTimestamp = version.created; - } - return release; - }, - ); + result.releases = versions.map(({ version, releaseTimestamp }) => { + const release: Release = { version }; + if (releaseTimestamp) { + release.releaseTimestamp = releaseTimestamp; + } + return release; + }); return result; } diff --git a/lib/modules/datasource/galaxy/schema.ts b/lib/modules/datasource/galaxy/schema.ts index 7fb519004908e3..2366ffa67b1c95 100644 --- a/lib/modules/datasource/galaxy/schema.ts +++ b/lib/modules/datasource/galaxy/schema.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { MaybeTimestamp } from '../../../util/timestamp'; export type GalaxyV1 = z.infer; export const GalaxyV1 = z.object({ @@ -6,10 +7,15 @@ export const GalaxyV1 = z.object({ z.object({ summary_fields: z.object({ versions: z.array( - z.object({ - name: z.string(), - created: z.string().optional(), - }), + z + .object({ + name: z.string(), + created: MaybeTimestamp, + }) + .transform(({ name, created }) => ({ + version: name, + releaseTimestamp: created, + })), ), }), github_user: z.string().optional(), diff --git a/lib/modules/datasource/gitea-releases/readme.md b/lib/modules/datasource/gitea-releases/readme.md index 7ad6f52c7e467b..e0d122fdfa27c3 100644 --- a/lib/modules/datasource/gitea-releases/readme.md +++ b/lib/modules/datasource/gitea-releases/readme.md @@ -1,3 +1,3 @@ -This datasource allows to lookup releases from a [Gitea](https://about.gitea.com/) or [Forjeo](https://forgejo.org/) repositories. +This datasource allows to lookup releases from a [Gitea](https://about.gitea.com/) or [Forgejo](https://forgejo.org/) repositories. By default it will use `https://gitea.com` to lookup releases. diff --git a/lib/modules/datasource/gitea-releases/schema.ts b/lib/modules/datasource/gitea-releases/schema.ts index fe57281c672d57..b1482c99701e63 100644 --- a/lib/modules/datasource/gitea-releases/schema.ts +++ b/lib/modules/datasource/gitea-releases/schema.ts @@ -1,11 +1,12 @@ import { z } from 'zod'; +import { MaybeTimestamp } from '../../../util/timestamp'; export const ReleaseSchema = z.object({ name: z.string(), tag_name: z.string(), body: z.string(), prerelease: z.boolean(), - published_at: z.string().datetime({ offset: true }), + published_at: MaybeTimestamp, }); export const ReleasesSchema = z.array(ReleaseSchema); diff --git a/lib/modules/datasource/gitea-tags/readme.md b/lib/modules/datasource/gitea-tags/readme.md index 8fe820ca3357a5..570dd40a5d7672 100644 --- a/lib/modules/datasource/gitea-tags/readme.md +++ b/lib/modules/datasource/gitea-tags/readme.md @@ -1,3 +1,3 @@ -This datasource allows to lookup git tags from a [Gitea](https://about.gitea.com/) or [Forjeo](https://forgejo.org/) repositories. +This datasource allows to lookup git tags from a [Gitea](https://about.gitea.com/) or [Forgejo](https://forgejo.org/) repositories. By default it will use `https://gitea.com` to lookup tags. diff --git a/lib/modules/datasource/gitea-tags/schema.ts b/lib/modules/datasource/gitea-tags/schema.ts index 603730de8f1dbf..3ba004a0bb1062 100644 --- a/lib/modules/datasource/gitea-tags/schema.ts +++ b/lib/modules/datasource/gitea-tags/schema.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { MaybeTimestamp } from '../../../util/timestamp'; export const CommitSchema = z.object({ sha: z.string(), @@ -8,7 +9,7 @@ export const CommitsSchema = z.array(CommitSchema); const TagCommitSchema = z.object({ sha: z.string(), - created: z.string().datetime({ offset: true }), + created: MaybeTimestamp, }); export const TagSchema = z.object({ diff --git a/lib/modules/datasource/github-release-attachments/index.spec.ts b/lib/modules/datasource/github-release-attachments/index.spec.ts index f1c17ad42c9102..02872612d4d86d 100644 --- a/lib/modules/datasource/github-release-attachments/index.spec.ts +++ b/lib/modules/datasource/github-release-attachments/index.spec.ts @@ -3,6 +3,7 @@ import { getDigest, getPkgReleases } from '..'; import { mocked } from '../../../../test/util'; import * as githubGraphql from '../../../util/github/graphql'; import * as _hostRules from '../../../util/host-rules'; +import type { Timestamp } from '../../../util/timestamp'; import { GitHubReleaseAttachmentMocker } from './test'; import { GithubReleaseAttachmentsDatasource } from '.'; @@ -28,7 +29,7 @@ describe('modules/datasource/github-release-attachments/index', () => { name: 'some/dep2', description: 'some description', version: 'a', - releaseTimestamp: '2020-03-09T13:00:00Z', + releaseTimestamp: '2020-03-09T13:00:00Z' as Timestamp, }, { id: 2, @@ -36,7 +37,7 @@ describe('modules/datasource/github-release-attachments/index', () => { name: 'some/dep2', description: 'some description', version: 'v', - releaseTimestamp: '2020-03-09T12:00:00Z', + releaseTimestamp: '2020-03-09T12:00:00Z' as Timestamp, }, { id: 3, @@ -44,7 +45,7 @@ describe('modules/datasource/github-release-attachments/index', () => { name: 'some/dep2', description: 'some description', version: '1.0.0', - releaseTimestamp: '2020-03-09T11:00:00Z', + releaseTimestamp: '2020-03-09T11:00:00Z' as Timestamp, }, { id: 4, @@ -52,7 +53,7 @@ describe('modules/datasource/github-release-attachments/index', () => { name: 'some/dep2', description: 'some description', version: 'v1.1.0', - releaseTimestamp: '2020-03-09T10:00:00Z', + releaseTimestamp: '2020-03-09T10:00:00Z' as Timestamp, }, { id: 5, @@ -60,7 +61,7 @@ describe('modules/datasource/github-release-attachments/index', () => { name: 'some/dep2', description: 'some description', version: '2.0.0', - releaseTimestamp: '2020-04-09T10:00:00Z', + releaseTimestamp: '2020-04-09T10:00:00Z' as Timestamp, isStable: false, }, ]); diff --git a/lib/modules/datasource/github-release-attachments/index.ts b/lib/modules/datasource/github-release-attachments/index.ts index 14caabb969ce1d..2b95ca53fea59b 100644 --- a/lib/modules/datasource/github-release-attachments/index.ts +++ b/lib/modules/datasource/github-release-attachments/index.ts @@ -210,9 +210,10 @@ export class GithubReleaseAttachmentsDatasource extends Datasource { } const apiBaseUrl = getApiBaseUrl(registryUrl); - const { body: currentRelease } = await this.http.getJson( - `${apiBaseUrl}repos/${repo}/releases/tags/${currentValue}`, - ); + const { body: currentRelease } = + await this.http.getJsonUnchecked( + `${apiBaseUrl}repos/${repo}/releases/tags/${currentValue}`, + ); const digestAsset = await this.findDigestAsset( currentRelease, currentDigest, @@ -221,9 +222,10 @@ export class GithubReleaseAttachmentsDatasource extends Datasource { if (!digestAsset || newValue === currentValue) { newDigest = currentDigest; } else { - const { body: newRelease } = await this.http.getJson( - `${apiBaseUrl}repos/${repo}/releases/tags/${newValue}`, - ); + const { body: newRelease } = + await this.http.getJsonUnchecked( + `${apiBaseUrl}repos/${repo}/releases/tags/${newValue}`, + ); newDigest = await this.mapDigestAssetToRelease(digestAsset, newRelease); } return newDigest; diff --git a/lib/modules/datasource/github-releases/index.spec.ts b/lib/modules/datasource/github-releases/index.spec.ts index e6f93f07803bcd..fbd1f962a94958 100644 --- a/lib/modules/datasource/github-releases/index.spec.ts +++ b/lib/modules/datasource/github-releases/index.spec.ts @@ -3,6 +3,7 @@ import { getDigest, getPkgReleases } from '..'; import { mocked } from '../../../../test/util'; import * as githubGraphql from '../../../util/github/graphql'; import * as _hostRules from '../../../util/host-rules'; +import type { Timestamp } from '../../../util/timestamp'; import { GithubReleasesDatasource } from '.'; jest.mock('../../../util/host-rules', () => mockDeep()); @@ -25,7 +26,7 @@ describe('modules/datasource/github-releases/index', () => { name: 'some/dep2', description: 'some description', version: 'a', - releaseTimestamp: '2020-03-09T13:00:00Z', + releaseTimestamp: '2020-03-09T13:00:00Z' as Timestamp, }, { id: 2, @@ -33,7 +34,7 @@ describe('modules/datasource/github-releases/index', () => { name: 'some/dep2', description: 'some description', version: 'v', - releaseTimestamp: '2020-03-09T12:00:00Z', + releaseTimestamp: '2020-03-09T12:00:00Z' as Timestamp, }, { id: 3, @@ -41,7 +42,7 @@ describe('modules/datasource/github-releases/index', () => { name: 'some/dep2', description: 'some description', version: '1.0.0', - releaseTimestamp: '2020-03-09T11:00:00Z', + releaseTimestamp: '2020-03-09T11:00:00Z' as Timestamp, }, { id: 4, @@ -49,7 +50,7 @@ describe('modules/datasource/github-releases/index', () => { name: 'some/dep2', description: 'some description', version: 'v1.1.0', - releaseTimestamp: '2020-03-09T10:00:00Z', + releaseTimestamp: '2020-03-09T10:00:00Z' as Timestamp, }, { id: 5, @@ -57,7 +58,7 @@ describe('modules/datasource/github-releases/index', () => { name: 'some/dep2', description: 'some description', version: '2.0.0', - releaseTimestamp: '2020-04-09T10:00:00Z', + releaseTimestamp: '2020-04-09T10:00:00Z' as Timestamp, isStable: false, }, ]); @@ -70,11 +71,17 @@ describe('modules/datasource/github-releases/index', () => { expect(res).toMatchObject({ registryUrl: 'https://github.com', releases: [ - { releaseTimestamp: '2020-03-09T11:00:00.000Z', version: '1.0.0' }, - { version: 'v1.1.0', releaseTimestamp: '2020-03-09T10:00:00.000Z' }, + { + releaseTimestamp: '2020-03-09T11:00:00.000Z' as Timestamp, + version: '1.0.0', + }, + { + version: 'v1.1.0', + releaseTimestamp: '2020-03-09T10:00:00.000Z' as Timestamp, + }, { version: '2.0.0', - releaseTimestamp: '2020-04-09T10:00:00.000Z', + releaseTimestamp: '2020-04-09T10:00:00.000Z' as Timestamp, isStable: false, }, ], @@ -95,13 +102,13 @@ describe('modules/datasource/github-releases/index', () => { { version: 'v1.0.0', gitRef: 'v1.0.0', - releaseTimestamp: '2021-01-01', + releaseTimestamp: '2021-01-01' as Timestamp, hash: 'sha-of-v1', }, { version: 'v15.0.0', gitRef: 'v15.0.0', - releaseTimestamp: '2022-10-01', + releaseTimestamp: '2022-10-01' as Timestamp, hash: 'sha-of-v15', }, ]); diff --git a/lib/modules/datasource/github-runners/index.spec.ts b/lib/modules/datasource/github-runners/index.spec.ts index 7d79c59aca7e0d..4a5407e6630d26 100644 --- a/lib/modules/datasource/github-runners/index.spec.ts +++ b/lib/modules/datasource/github-runners/index.spec.ts @@ -14,7 +14,9 @@ describe('modules/datasource/github-runners/index', () => { { version: '16.04', isDeprecated: true }, { version: '18.04', isDeprecated: true }, { version: '20.04', isDeprecated: true }, + { version: '22.04-arm', isStable: false }, { version: '22.04' }, + { version: '24.04-arm', isStable: false }, { version: '24.04' }, ], 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 08a42b3f92c115..2c86263dbafab6 100644 --- a/lib/modules/datasource/github-runners/index.ts +++ b/lib/modules/datasource/github-runners/index.ts @@ -14,11 +14,14 @@ export class GithubRunnersDatasource extends Datasource { * Deprecated runners must have the `isDeprecated: true` property. * Stable runners should have no extra properties. * For more details, read the github-runners datasource readme. + * Check https://github.blog/changelog/label/actions/ for stable and deprecation dates. */ private static readonly releases: Record = { ubuntu: [ { version: '24.04' }, + { version: '24.04-arm', isStable: false }, { version: '22.04' }, + { version: '22.04-arm', isStable: false }, { version: '20.04', isDeprecated: true }, { version: '18.04', isDeprecated: true }, { version: '16.04', isDeprecated: true }, diff --git a/lib/modules/datasource/github-runners/readme.md b/lib/modules/datasource/github-runners/readme.md index 4c3d92bb3249e9..b73a94d53bf8c1 100644 --- a/lib/modules/datasource/github-runners/readme.md +++ b/lib/modules/datasource/github-runners/readme.md @@ -1,5 +1,5 @@ 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). +The datasource is based on GitHub's [`runner-images`](https://github.com/actions/runner-images) and [`partner-runner-images`](https://github.com/actions/partner-runner-images) repositories. Examples: `windows-2022` / `ubuntu-24.04` / `macos-14` diff --git a/lib/modules/datasource/github-tags/index.spec.ts b/lib/modules/datasource/github-tags/index.spec.ts index 8bde1f8d3fec43..02b2d0b9dcda44 100644 --- a/lib/modules/datasource/github-tags/index.spec.ts +++ b/lib/modules/datasource/github-tags/index.spec.ts @@ -4,6 +4,7 @@ import { partial } from '../../../../test/util'; import * as githubGraphql from '../../../util/github/graphql'; import type { GithubTagItem } from '../../../util/github/graphql/types'; import * as hostRules from '../../../util/host-rules'; +import type { Timestamp } from '../../../util/timestamp'; import { GithubTagsDatasource } from '.'; const githubApiHost = 'https://api.github.com'; @@ -55,13 +56,13 @@ describe('modules/datasource/github-tags/index', () => { { version: 'v1.0.0', gitRef: 'v1.0.0', - releaseTimestamp: '2021-01-01', + releaseTimestamp: '2021-01-01' as Timestamp, hash: '123', }, { version: 'v2.0.0', gitRef: 'v2.0.0', - releaseTimestamp: '2022-01-01', + releaseTimestamp: '2022-01-01' as Timestamp, hash: 'abc', }, ]); @@ -74,13 +75,13 @@ describe('modules/datasource/github-tags/index', () => { { version: 'v1.0.0', gitRef: 'v1.0.0', - releaseTimestamp: '2021-01-01', + releaseTimestamp: '2021-01-01' as Timestamp, hash: '123', }, partial({ version: 'v2.0.0', gitRef: 'v2.0.0', - releaseTimestamp: '2022-01-01', + releaseTimestamp: '2022-01-01' as Timestamp, }), ]); const res = await github.getDigest({ packageName }, 'v2.0.0'); @@ -92,13 +93,13 @@ describe('modules/datasource/github-tags/index', () => { { version: 'v1.0.0', gitRef: 'v1.0.0', - releaseTimestamp: '2021-01-01', + releaseTimestamp: '2021-01-01' as Timestamp, hash: '123', }, { version: 'v2.0.0', gitRef: 'v2.0.0', - releaseTimestamp: '2022-01-01', + releaseTimestamp: '2022-01-01' as Timestamp, hash: 'abc', }, ]); @@ -121,13 +122,13 @@ describe('modules/datasource/github-tags/index', () => { { version: 'v1.0.0', gitRef: 'v1.0.0', - releaseTimestamp: '2021-01-01', + releaseTimestamp: '2021-01-01' as Timestamp, hash: '123', }, { version: 'v2.0.0', gitRef: 'v2.0.0', - releaseTimestamp: '2022-01-01', + releaseTimestamp: '2022-01-01' as Timestamp, hash: 'abc', }, ]); @@ -135,7 +136,7 @@ describe('modules/datasource/github-tags/index', () => { { id: 1, version: 'v1.0.0', - releaseTimestamp: '2021-01-01', + releaseTimestamp: '2021-01-01' as Timestamp, isStable: true, url: 'https://example.com', name: 'some/dep2', @@ -144,7 +145,7 @@ describe('modules/datasource/github-tags/index', () => { { id: 2, version: 'v2.0.0', - releaseTimestamp: '2022-01-01', + releaseTimestamp: '2022-01-01' as Timestamp, isStable: false, url: 'https://example.com', name: 'some/dep2', diff --git a/lib/modules/datasource/github-tags/index.ts b/lib/modules/datasource/github-tags/index.ts index 0fdca9b6b3020d..38871793034609 100644 --- a/lib/modules/datasource/github-tags/index.ts +++ b/lib/modules/datasource/github-tags/index.ts @@ -43,7 +43,7 @@ export class GithubTagsDatasource extends Datasource { let digest: string | null = null; try { const url = `${apiBaseUrl}repos/${githubRepo}/commits?per_page=1`; - const res = await this.http.getJson<{ sha: string }[]>(url); + const res = await this.http.getJsonUnchecked<{ sha: string }[]>(url); digest = res.body[0].sha; } catch (err) { logger.debug( diff --git a/lib/modules/datasource/gitlab-packages/index.ts b/lib/modules/datasource/gitlab-packages/index.ts index 28d0483aae5069..fa2bfa27fdcee1 100644 --- a/lib/modules/datasource/gitlab-packages/index.ts +++ b/lib/modules/datasource/gitlab-packages/index.ts @@ -1,5 +1,6 @@ import { cache } from '../../../util/cache/package/decorator'; import { GitlabHttp } from '../../../util/http/gitlab'; +import { asTimestamp } from '../../../util/timestamp'; import { joinUrlParts } from '../../../util/url'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, ReleaseResult } from '../types'; @@ -74,7 +75,9 @@ export class GitlabPackagesDatasource extends Datasource { let response: GitlabPackage[]; try { response = ( - await this.http.getJson(apiUrl, { paginate: true }) + await this.http.getJsonUnchecked(apiUrl, { + paginate: true, + }) ).body; result.releases = response @@ -83,7 +86,7 @@ export class GitlabPackagesDatasource extends Datasource { .filter((r) => r.name === packagePart) .map(({ version, created_at }) => ({ version, - releaseTimestamp: created_at, + releaseTimestamp: asTimestamp(created_at), })); } catch (err) { this.handleGenericErrors(err); diff --git a/lib/modules/datasource/gitlab-releases/index.ts b/lib/modules/datasource/gitlab-releases/index.ts index 54ba562796a795..547cd5e5b7b599 100644 --- a/lib/modules/datasource/gitlab-releases/index.ts +++ b/lib/modules/datasource/gitlab-releases/index.ts @@ -1,5 +1,6 @@ import { cache } from '../../../util/cache/package/decorator'; import { GitlabHttp } from '../../../util/http/gitlab'; +import { asTimestamp } from '../../../util/timestamp'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; import type { GitlabRelease } from './types'; @@ -43,7 +44,7 @@ export class GitlabReleasesDatasource extends Datasource { try { const gitlabReleasesResponse = ( - await this.http.getJson(apiUrl) + await this.http.getJsonUnchecked(apiUrl) ).body; return { @@ -53,7 +54,7 @@ export class GitlabReleasesDatasource extends Datasource { registryUrl, gitRef: tag_name, version: tag_name, - releaseTimestamp: released_at, + releaseTimestamp: asTimestamp(released_at), }; return release; }), diff --git a/lib/modules/datasource/gitlab-tags/index.ts b/lib/modules/datasource/gitlab-tags/index.ts index 89ba14da0dc5f6..a4a23fadec508b 100644 --- a/lib/modules/datasource/gitlab-tags/index.ts +++ b/lib/modules/datasource/gitlab-tags/index.ts @@ -1,6 +1,7 @@ import { logger } from '../../../logger'; import { cache } from '../../../util/cache/package/decorator'; import { GitlabHttp } from '../../../util/http/gitlab'; +import { asTimestamp } from '../../../util/timestamp'; import { joinUrlParts } from '../../../util/url'; import { Datasource } from '../datasource'; import type { DigestConfig, GetReleasesConfig, ReleaseResult } from '../types'; @@ -48,7 +49,7 @@ export class GitlabTagsDatasource extends Datasource { ); const gitlabTags = ( - await this.http.getJson(url, { + await this.http.getJsonUnchecked(url, { paginate: true, }) ).body; @@ -60,7 +61,7 @@ export class GitlabTagsDatasource extends Datasource { dependency.releases = gitlabTags.map(({ name, commit }) => ({ version: name, gitRef: name, - releaseTimestamp: commit?.created_at, + releaseTimestamp: asTimestamp(commit?.created_at), })); return dependency; @@ -94,7 +95,8 @@ export class GitlabTagsDatasource extends Datasource { `repository/commits/`, newValue, ); - const gitlabCommits = await this.http.getJson(url); + const gitlabCommits = + await this.http.getJsonUnchecked(url); digest = gitlabCommits.body.id; } else { const url = joinUrlParts( @@ -103,7 +105,8 @@ export class GitlabTagsDatasource extends Datasource { urlEncodedRepo, `repository/commits?per_page=1`, ); - const gitlabCommits = await this.http.getJson(url); + const gitlabCommits = + await this.http.getJsonUnchecked(url); digest = gitlabCommits.body[0].id; } } catch (err) { diff --git a/lib/modules/datasource/glasskube-packages/index.ts b/lib/modules/datasource/glasskube-packages/index.ts index a289e79682dfa5..f36daeb381bf14 100644 --- a/lib/modules/datasource/glasskube-packages/index.ts +++ b/lib/modules/datasource/glasskube-packages/index.ts @@ -3,11 +3,7 @@ import { joinUrlParts } from '../../../util/url'; import * as glasskubeVersioning from '../../versioning/glasskube'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, ReleaseResult } from '../types'; -import type { GlasskubePackageVersions } from './schema'; -import { - GlasskubePackageManifestYaml, - GlasskubePackageVersionsYaml, -} from './schema'; +import { GlasskubePackageManifest, GlasskubePackageVersions } from './schema'; export class GlasskubePackagesDatasource extends Datasource { static readonly id = 'glasskube-packages'; @@ -33,16 +29,17 @@ export class GlasskubePackagesDatasource extends Datasource { packageName, registryUrl, }: GetReleasesConfig): Promise { - let versions: GlasskubePackageVersions; const result: ReleaseResult = { releases: [] }; - try { - const response = await this.http.get( + const { val: versions, err: versionsErr } = await this.http + .getYamlSafe( joinUrlParts(registryUrl!, packageName, 'versions.yaml'), - ); - versions = GlasskubePackageVersionsYaml.parse(response.body); - } catch (err) { - this.handleGenericErrors(err); + GlasskubePackageVersions, + ) + .unwrap(); + + if (versionsErr) { + this.handleGenericErrors(versionsErr); } result.releases = versions.versions.map((it) => ({ @@ -50,25 +47,28 @@ export class GlasskubePackagesDatasource extends Datasource { })); result.tags = { latest: versions.latestVersion }; - try { - const response = await this.http.get( + const { val: latestManifest, err: latestManifestErr } = await this.http + .getYamlSafe( joinUrlParts( registryUrl!, packageName, versions.latestVersion, 'package.yaml', ), - ); - const latestManifest = GlasskubePackageManifestYaml.parse(response.body); - for (const ref of latestManifest?.references ?? []) { - if (ref.label.toLowerCase() === 'github') { - result.sourceUrl = ref.url; - } else if (ref.label.toLowerCase() === 'website') { - result.homepage = ref.url; - } + GlasskubePackageManifest, + ) + .unwrap(); + + if (latestManifestErr) { + this.handleGenericErrors(latestManifestErr); + } + + for (const ref of latestManifest?.references ?? []) { + if (ref.label.toLowerCase() === 'github') { + result.sourceUrl = ref.url; + } else if (ref.label.toLowerCase() === 'website') { + result.homepage = ref.url; } - } catch (err) { - this.handleGenericErrors(err); } return result; diff --git a/lib/modules/datasource/glasskube-packages/schema.ts b/lib/modules/datasource/glasskube-packages/schema.ts index 5e299304b4104f..e459912c14bfb4 100644 --- a/lib/modules/datasource/glasskube-packages/schema.ts +++ b/lib/modules/datasource/glasskube-packages/schema.ts @@ -1,12 +1,11 @@ import { z } from 'zod'; -import { Yaml } from '../../../util/schema-utils'; -const GlasskubePackageVersions = z.object({ +export const GlasskubePackageVersions = z.object({ latestVersion: z.string(), versions: z.array(z.object({ version: z.string() })), }); -const GlasskubePackageManifest = z.object({ +export const GlasskubePackageManifest = z.object({ references: z.optional( z.array( z.object({ @@ -16,8 +15,3 @@ const GlasskubePackageManifest = z.object({ ), ), }); - -export const GlasskubePackageVersionsYaml = Yaml.pipe(GlasskubePackageVersions); -export const GlasskubePackageManifestYaml = Yaml.pipe(GlasskubePackageManifest); - -export type GlasskubePackageVersions = z.infer; diff --git a/lib/modules/datasource/go/__snapshots__/releases-direct.spec.ts.snap b/lib/modules/datasource/go/__snapshots__/releases-direct.spec.ts.snap index 4e65ba27707cd4..d70e507a08ad46 100644 --- a/lib/modules/datasource/go/__snapshots__/releases-direct.spec.ts.snap +++ b/lib/modules/datasource/go/__snapshots__/releases-direct.spec.ts.snap @@ -6,12 +6,12 @@ exports[`modules/datasource/go/releases-direct getReleases support bitbucket tag "releases": [ { "gitRef": "v1.0.0", - "releaseTimestamp": undefined, + "releaseTimestamp": null, "version": "v1.0.0", }, { "gitRef": "v2.0.0", - "releaseTimestamp": undefined, + "releaseTimestamp": null, "version": "v2.0.0", }, ], @@ -40,12 +40,12 @@ exports[`modules/datasource/go/releases-direct getReleases support gitlab 1`] = "releases": [ { "gitRef": "v1.0.0", - "releaseTimestamp": undefined, + "releaseTimestamp": null, "version": "v1.0.0", }, { "gitRef": "v2.0.0", - "releaseTimestamp": undefined, + "releaseTimestamp": null, "version": "v2.0.0", }, ], @@ -58,12 +58,12 @@ exports[`modules/datasource/go/releases-direct getReleases support gitlab subgro "releases": [ { "gitRef": "v1.0.0", - "releaseTimestamp": undefined, + "releaseTimestamp": null, "version": "v1.0.0", }, { "gitRef": "v2.0.0", - "releaseTimestamp": undefined, + "releaseTimestamp": null, "version": "v2.0.0", }, ], @@ -76,12 +76,12 @@ exports[`modules/datasource/go/releases-direct getReleases support self hosted g "releases": [ { "gitRef": "v1.0.0", - "releaseTimestamp": undefined, + "releaseTimestamp": null, "version": "v1.0.0", }, { "gitRef": "v2.0.0", - "releaseTimestamp": undefined, + "releaseTimestamp": null, "version": "v2.0.0", }, ], diff --git a/lib/modules/datasource/go/releases-direct.spec.ts b/lib/modules/datasource/go/releases-direct.spec.ts index 19da04761d3c4a..c63003fa541a2b 100644 --- a/lib/modules/datasource/go/releases-direct.spec.ts +++ b/lib/modules/datasource/go/releases-direct.spec.ts @@ -129,19 +129,19 @@ describe('modules/datasource/go/releases-direct', () => { { gitRef: 'v0.1.0', newDigest: 'd73d815ec22c421e7192a414594ac798c73c89e5', - releaseTimestamp: '2022-05-15T16:29:42Z', + releaseTimestamp: '2022-05-15T16:29:42.000Z', version: 'v0.1.0', }, { gitRef: 'v0.2.0', newDigest: '3976707232cb68751ff2ddf42547ff95c6878a97', - releaseTimestamp: '2022-05-15T17:23:28Z', + releaseTimestamp: '2022-05-15T17:23:28.000Z', version: 'v0.2.0', }, { gitRef: 'v0.2.1', newDigest: '2963b104773ead7ed28c00181c03318885d909dc', - releaseTimestamp: '2024-09-06T23:44:34Z', + releaseTimestamp: '2024-09-06T23:44:34.000Z', version: 'v0.2.1', }, ], diff --git a/lib/modules/datasource/go/releases-goproxy.spec.ts b/lib/modules/datasource/go/releases-goproxy.spec.ts index 2d6950d057f79e..70c5287f7b6f60 100644 --- a/lib/modules/datasource/go/releases-goproxy.spec.ts +++ b/lib/modules/datasource/go/releases-goproxy.spec.ts @@ -64,7 +64,7 @@ describe('modules/datasource/go/releases-goproxy', () => { expect(release).toEqual({ version: 'v0.5.0', - releaseTimestamp: '2017-06-08T17:28:36Z', + releaseTimestamp: '2017-06-08T17:28:36.000Z', }); }); }); @@ -155,15 +155,15 @@ describe('modules/datasource/go/releases-goproxy', () => { releases: [ { version: 'v1.0.0', - releaseTimestamp: '2018-08-13T15:31:12Z', + releaseTimestamp: '2018-08-13T15:31:12.000Z', }, { version: 'v1.0.1', - releaseTimestamp: '2019-10-16T16:15:28Z', + releaseTimestamp: '2019-10-16T16:15:28.000Z', }, { version: 'v1.28.1-20230721020619-4464c06fa399.4', - releaseTimestamp: '2023-07-21T02:06:19Z', + releaseTimestamp: '2023-07-21T02:06:19.000Z', newDigest: '4464c06fa399', }, ], @@ -245,8 +245,8 @@ describe('modules/datasource/go/releases-goproxy', () => { expect(res).toEqual({ releases: [ - { releaseTimestamp: '2018-08-13T15:31:12Z', version: 'v1.0.0' }, - { releaseTimestamp: '2019-10-16T16:15:28Z', version: 'v1.0.1' }, + { releaseTimestamp: '2018-08-13T15:31:12.000Z', version: 'v1.0.0' }, + { releaseTimestamp: '2019-10-16T16:15:28.000Z', version: 'v1.0.1' }, ], sourceUrl: 'https://github.com/google/btree', tags: { latest: 'v1.0.1' }, @@ -296,8 +296,8 @@ describe('modules/datasource/go/releases-goproxy', () => { expect(res).toEqual({ releases: [ - { releaseTimestamp: '2018-08-13T15:31:12Z', version: 'v1.0.0' }, - { releaseTimestamp: '2019-10-16T16:15:28Z', version: 'v1.0.1' }, + { releaseTimestamp: '2018-08-13T15:31:12.000Z', version: 'v1.0.0' }, + { releaseTimestamp: '2019-10-16T16:15:28.000Z', version: 'v1.0.1' }, ], sourceUrl: 'https://github.com/google/btree', tags: { latest: 'v1.0.1' }, @@ -399,7 +399,7 @@ describe('modules/datasource/go/releases-goproxy', () => { httpMock .scope(`${baseUrl}/custom.com/lib/btree`) .get('/@v/list') - .reply(200, ['v1.0.0 2018-08-13T15:31:12Z', 'v1.0.1'].join('\n')) + .reply(200, ['v1.0.0 2018-08-13T15:31:12.000Z', 'v1.0.1'].join('\n')) .get('/@v/v1.0.1.info') .reply(200, { Version: 'v1.0.1', Time: '2019-10-16T16:15:28Z' }) .get('/@latest') @@ -417,8 +417,8 @@ describe('modules/datasource/go/releases-goproxy', () => { expect(res).toEqual({ releases: [ - { releaseTimestamp: '2018-08-13T15:31:12Z', version: 'v1.0.0' }, - { releaseTimestamp: '2019-10-16T16:15:28Z', version: 'v1.0.1' }, + { releaseTimestamp: '2018-08-13T15:31:12.000Z', version: 'v1.0.0' }, + { releaseTimestamp: '2019-10-16T16:15:28.000Z', version: 'v1.0.1' }, ], tags: { latest: 'v1.0.1' }, }); @@ -470,9 +470,9 @@ describe('modules/datasource/go/releases-goproxy', () => { expect(res).toEqual({ releases: [ - { releaseTimestamp: '2018-08-13T15:31:12Z', version: 'v1.0.0' }, - { releaseTimestamp: '2019-10-16T16:15:28Z', version: 'v1.0.1' }, - { releaseTimestamp: '2020-10-16T16:15:28Z', version: 'v2.0.0' }, + { releaseTimestamp: '2018-08-13T15:31:12.000Z', version: 'v1.0.0' }, + { releaseTimestamp: '2019-10-16T16:15:28.000Z', version: 'v1.0.1' }, + { releaseTimestamp: '2020-10-16T16:15:28.000Z', version: 'v2.0.0' }, ], sourceUrl: 'https://github.com/google/btree', tags: { latest: 'v2.0.0' }, @@ -513,10 +513,10 @@ describe('modules/datasource/go/releases-goproxy', () => { expect(res).toEqual({ releases: [ - { releaseTimestamp: '2020-05-06T23:08:38Z', version: 'v2.3.0' }, - { releaseTimestamp: '2020-11-17T15:46:20Z', version: 'v2.4.0' }, - { releaseTimestamp: '2022-05-21T10:33:21Z', version: 'v3.0.0' }, - { releaseTimestamp: '2022-05-27T08:35:30Z', version: 'v3.0.1' }, + { releaseTimestamp: '2020-05-06T23:08:38.000Z', version: 'v2.3.0' }, + { releaseTimestamp: '2020-11-17T15:46:20.000Z', version: 'v2.4.0' }, + { releaseTimestamp: '2022-05-21T10:33:21.000Z', version: 'v3.0.0' }, + { releaseTimestamp: '2022-05-27T08:35:30.000Z', version: 'v3.0.1' }, ], sourceUrl: 'https://github.com/go-yaml/yaml', tags: { latest: 'v3.0.1' }, @@ -551,9 +551,9 @@ describe('modules/datasource/go/releases-goproxy', () => { expect(res).toEqual({ releases: [ - { releaseTimestamp: '2017-01-01T00:00:00Z', version: 'v0.1.0' }, - { releaseTimestamp: '2017-02-01T00:00:00Z', version: 'v0.2.0' }, - { releaseTimestamp: '2018-01-01T00:00:00Z', version: 'v1.0.0' }, + { releaseTimestamp: '2017-01-01T00:00:00.000Z', version: 'v0.1.0' }, + { releaseTimestamp: '2017-02-01T00:00:00.000Z', version: 'v0.2.0' }, + { releaseTimestamp: '2018-01-01T00:00:00.000Z', version: 'v1.0.0' }, ], sourceUrl: 'https://github.com/go-foo/foo', tags: { latest: 'v1.0.0' }, @@ -588,9 +588,9 @@ describe('modules/datasource/go/releases-goproxy', () => { expect(res).toEqual({ releases: [ - { releaseTimestamp: '2017-01-01T00:00:00Z', version: 'v0.1.0' }, - { releaseTimestamp: '2017-02-01T00:00:00Z', version: 'v0.2.0' }, - { releaseTimestamp: '2018-01-01T00:00:00Z', version: 'v1.0.0' }, + { releaseTimestamp: '2017-01-01T00:00:00.000Z', version: 'v0.1.0' }, + { releaseTimestamp: '2017-02-01T00:00:00.000Z', version: 'v0.2.0' }, + { releaseTimestamp: '2018-01-01T00:00:00.000Z', version: 'v1.0.0' }, ], sourceUrl: 'https://github.com/go-foo/foo', tags: { latest: 'v1.0.0' }, @@ -632,7 +632,7 @@ describe('modules/datasource/go/releases-goproxy', () => { releases: [ { newDigest: '921286631fa9', - releaseTimestamp: '2023-09-05T20:02:55Z', + releaseTimestamp: '2023-09-05T20:02:55.000Z', version: 'v0.0.0-20230905200255-921286631fa9', }, ], diff --git a/lib/modules/datasource/go/releases-goproxy.ts b/lib/modules/datasource/go/releases-goproxy.ts index 1a2a9d301cbf1e..e3f87682a3478f 100644 --- a/lib/modules/datasource/go/releases-goproxy.ts +++ b/lib/modules/datasource/go/releases-goproxy.ts @@ -1,5 +1,4 @@ import is from '@sindresorhus/is'; -import { DateTime } from 'luxon'; import { logger } from '../../../logger'; import { ExternalHostError } from '../../../types/errors/external-host-error'; import { cache } from '../../../util/cache/package/decorator'; @@ -7,6 +6,7 @@ import { filterMap } from '../../../util/filter-map'; import { HttpError } from '../../../util/http'; import * as p from '../../../util/promises'; import { newlineRegex, regEx } from '../../../util/regex'; +import { asTimestamp } from '../../../util/timestamp'; import { joinUrlParts } from '../../../util/url'; import goVersioning from '../../versioning/go-mod-directive'; import { Datasource } from '../datasource'; @@ -33,9 +33,7 @@ export function pseudoVersionToRelease(pseudoVersion: string): Release | null { } const { digest: newDigest, timestamp } = match; - const releaseTimestamp = DateTime.fromFormat(timestamp, 'yyyyMMddHHmmss', { - zone: 'UTC', - }).toISO({ suppressMilliseconds: true }); + const releaseTimestamp = asTimestamp(timestamp); return { version: pseudoVersion, @@ -142,9 +140,10 @@ export class GoProxyDatasource extends Datasource { return null; } - const [version, releaseTimestamp] = str.trim().split(regEx(/\s+/)); + const [version, timestamp] = str.trim().split(regEx(/\s+/)); const release: Release = pseudoVersionToRelease(version) ?? { version }; + const releaseTimestamp = asTimestamp(timestamp); if (releaseTimestamp) { release.releaseTimestamp = releaseTimestamp; } @@ -164,14 +163,15 @@ export class GoProxyDatasource extends Datasource { '@v', `${version}.info`, ); - const res = await this.http.getJson(url); + const res = await this.http.getJsonUnchecked(url); const result: Release = { version: res.body.Version, }; - if (res.body.Time) { - result.releaseTimestamp = res.body.Time; + const releaseTimestamp = asTimestamp(res.body.Time); + if (releaseTimestamp) { + result.releaseTimestamp = releaseTimestamp; } return result; @@ -187,7 +187,7 @@ export class GoProxyDatasource extends Datasource { this.encodeCase(packageName), '@latest', ); - const res = await this.http.getJson(url); + const res = await this.http.getJsonUnchecked(url); return res.body.Version; } catch (err) { logger.trace({ err }, 'Failed to get latest version'); diff --git a/lib/modules/datasource/golang-version/index.ts b/lib/modules/datasource/golang-version/index.ts index 65e7d9b87b37c3..6d355a60aeb4bc 100644 --- a/lib/modules/datasource/golang-version/index.ts +++ b/lib/modules/datasource/golang-version/index.ts @@ -1,6 +1,7 @@ import { ExternalHostError } from '../../../types/errors/external-host-error'; import { cache } from '../../../util/cache/package/decorator'; import { regEx } from '../../../util/regex'; +import { asTimestamp } from '../../../util/timestamp'; import { joinUrlParts } from '../../../util/url'; import { isVersion, id as semverVersioningId } from '../../versioning/semver'; import { Datasource } from '../datasource'; @@ -111,7 +112,9 @@ export class GolangVersionDatasource extends Datasource { const year = releaseDateMatch.groups.year.padStart(4, '0'); const month = releaseDateMatch.groups.month.padStart(2, '0'); const day = releaseDateMatch.groups.day.padStart(2, '0'); - release.releaseTimestamp = `${year}-${month}-${day}T00:00:00.000Z`; + release.releaseTimestamp = asTimestamp( + `${year}-${month}-${day}T00:00:00.000Z`, + ); } const releaseVersionMatch = releaseVersionRegex.exec(line); if (releaseVersionMatch?.groups) { diff --git a/lib/modules/datasource/gradle-version/index.ts b/lib/modules/datasource/gradle-version/index.ts index bb6368e19803f1..d733216953e9cc 100644 --- a/lib/modules/datasource/gradle-version/index.ts +++ b/lib/modules/datasource/gradle-version/index.ts @@ -1,5 +1,5 @@ import { cache } from '../../../util/cache/package/decorator'; -import { regEx } from '../../../util/regex'; +import { asTimestamp } from '../../../util/timestamp'; import * as gradleVersioning from '../../versioning/gradle'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; @@ -27,10 +27,6 @@ export class GradleVersionDatasource extends Datasource { 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)$', - ); - @cache({ namespace: `datasource-${GradleVersionDatasource.id}`, // TODO: types (#22198) @@ -46,7 +42,8 @@ export class GradleVersionDatasource extends Datasource { let releases: Release[]; try { - const response = await this.http.getJson(registryUrl); + const response = + await this.http.getJsonUnchecked(registryUrl); releases = response.body .filter((release) => !release.snapshot && !release.nightly) .map((release) => { @@ -54,8 +51,7 @@ export class GradleVersionDatasource extends Datasource { const gitRef = GradleVersionDatasource.getGitRef(release.version); - const releaseTimestamp = - GradleVersionDatasource.formatBuildTime(buildTime); + const releaseTimestamp = asTimestamp(buildTime); const result: Release = { version, gitRef, releaseTimestamp }; @@ -80,19 +76,6 @@ export class GradleVersionDatasource extends Datasource { return null; } - private static formatBuildTime(timeStr: string): string | null { - if (!timeStr) { - return null; - } - if (GradleVersionDatasource.buildTimeRegex.test(timeStr)) { - return timeStr.replace( - GradleVersionDatasource.buildTimeRegex, - '$1-$2-$3T$4:$5:$6$7', - ); - } - return null; - } - /** * Calculate `gitTag` based on `version`: * - `8.1.2` -> `v8.1.2` diff --git a/lib/modules/datasource/hackage/index.spec.ts b/lib/modules/datasource/hackage/index.spec.ts index 676e082583da48..1235b179a91cde 100644 --- a/lib/modules/datasource/hackage/index.spec.ts +++ b/lib/modules/datasource/hackage/index.spec.ts @@ -8,7 +8,7 @@ describe('modules/datasource/hackage/index', () => { describe('versionToRelease', () => { it('should make release with given version', () => { expect( - versionToRelease('3.1.0', 'base', 'http://localhost').version, + versionToRelease('3.1.0', 'base', 'http://localhost', false).version, ).toBe('3.1.0'); }); }); @@ -33,11 +33,11 @@ describe('modules/datasource/hackage/index', () => { ).toBeNull(); }); - it('returns release for 200', async () => { + it('returns releases for 200', async () => { httpMock .scope(baseUrl) .get('/package/base.json') - .reply(200, { '4.20.0.1': 'normal' }); + .reply(200, { '4.19.0.1': 'deprecated', '4.20.0.1': 'normal' }); expect( await getPkgReleases({ datasource: HackageDatasource.id, @@ -46,9 +46,15 @@ describe('modules/datasource/hackage/index', () => { ).toEqual({ registryUrl: baseUrl, releases: [ + { + changelogUrl: baseUrl + 'package/base-4.19.0.1/changelog', + version: '4.19.0.1', + isDeprecated: true, + }, { changelogUrl: baseUrl + 'package/base-4.20.0.1/changelog', version: '4.20.0.1', + isDeprecated: false, }, ], }); diff --git a/lib/modules/datasource/hackage/index.ts b/lib/modules/datasource/hackage/index.ts index 4a75568d485468..481d92b56d4268 100644 --- a/lib/modules/datasource/hackage/index.ts +++ b/lib/modules/datasource/hackage/index.ts @@ -28,12 +28,14 @@ export class HackageDatasource extends Datasource { `${massagedPackageName}.json`, ); const res = await this.http.getJson(url, HackagePackageMetadata); - const keys = Object.keys(res.body); - return { - releases: keys.map((version) => - versionToRelease(version, packageName, registryUrl), - ), - }; + const releases = []; + for (const [version, versionStatus] of Object.entries(res.body)) { + const isDeprecated = versionStatus === 'deprecated'; + releases.push( + versionToRelease(version, packageName, registryUrl, isDeprecated), + ); + } + return { releases }; } } @@ -41,6 +43,7 @@ export function versionToRelease( version: string, packageName: string, registryUrl: string, + isDeprecated: boolean, ): Release { return { version, @@ -50,5 +53,6 @@ export function versionToRelease( `${packageName}-${version}`, 'changelog', ), + isDeprecated, }; } diff --git a/lib/modules/datasource/hackage/schema.ts b/lib/modules/datasource/hackage/schema.ts index dcee186743c602..4081d153f3b92a 100644 --- a/lib/modules/datasource/hackage/schema.ts +++ b/lib/modules/datasource/hackage/schema.ts @@ -1,3 +1,8 @@ import { z } from 'zod'; -export const HackagePackageMetadata = z.record(z.string()); +// See https://github.com/haskell/hackage-server +// revision e885d36c +// src/Distribution/Server/Features/PackageInfoJSON/State.hs line 160 +const VersionStatus = z.enum(['normal', 'deprecated', 'unpreferred']); + +export const HackagePackageMetadata = z.record(VersionStatus); diff --git a/lib/modules/datasource/helm/common.spec.ts b/lib/modules/datasource/helm/common.spec.ts deleted file mode 100644 index 84b88120db135a..00000000000000 --- a/lib/modules/datasource/helm/common.spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Fixtures } from '../../../../test/fixtures'; -import { parseSingleYaml } from '../../../util/yaml'; -import { findSourceUrl } from './common'; -import type { HelmRepository } from './types'; - -// Truncated index.yaml file -const repo = parseSingleYaml(Fixtures.get('sample.yaml')); - -describe('modules/datasource/helm/common', () => { - describe('findSourceUrl', () => { - it.each` - input | output - ${'airflow'} | ${'https://github.com/bitnami/charts/tree/master/bitnami/airflow'} - ${'coredns'} | ${'https://github.com/coredns/helm'} - ${'pgadmin4'} | ${'https://github.com/rowanruseler/helm-charts'} - ${'private-chart-github'} | ${'https://github.example.com/some-org/charts/tree/master/private-chart'} - ${'private-chart-gitlab'} | ${'https://gitlab.example.com/some/group/charts/-/tree/master/private-chart'} - ${'dummy'} | ${null} - `( - '$input -> $output', - ({ input, output }: { input: string; output: string }) => { - expect(findSourceUrl(repo.entries[input][0])).toEqual(output); - }, - ); - }); -}); diff --git a/lib/modules/datasource/helm/common.ts b/lib/modules/datasource/helm/common.ts deleted file mode 100644 index 0b32a3c33fa971..00000000000000 --- a/lib/modules/datasource/helm/common.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { detectPlatform } from '../../../util/common'; -import { parseGitUrl } from '../../../util/git/url'; -import { regEx } from '../../../util/regex'; -import type { HelmRelease } from './types'; - -const chartRepo = regEx(/charts?|helm|helm-charts/i); -const githubRelease = regEx( - /^(https:\/\/github\.com\/[^/]+\/[^/]+)\/releases\//, -); - -function isPossibleChartRepo(url: string): boolean { - if (detectPlatform(url) === null) { - return false; - } - - const parsed = parseGitUrl(url); - return chartRepo.test(parsed.name); -} - -export function findSourceUrl(release: HelmRelease): string | null { - // it's a github release :) - const releaseMatch = githubRelease.exec(release.urls[0]); - if (releaseMatch) { - return releaseMatch[1]; - } - - if (release.home && isPossibleChartRepo(release.home)) { - return release.home; - } - - if (!release.sources?.length) { - return null; - } - - for (const url of release.sources) { - if (isPossibleChartRepo(url)) { - return url; - } - } - - // fallback - return release.sources[0]; -} diff --git a/lib/modules/datasource/helm/index.ts b/lib/modules/datasource/helm/index.ts index 6ca3681c98103e..506b6a46904271 100644 --- a/lib/modules/datasource/helm/index.ts +++ b/lib/modules/datasource/helm/index.ts @@ -1,14 +1,11 @@ -import is from '@sindresorhus/is'; import { logger } from '../../../logger'; import { cache } from '../../../util/cache/package/decorator'; -import type { HttpResponse } from '../../../util/http/types'; import { ensureTrailingSlash } from '../../../util/url'; -import { parseSingleYaml } from '../../../util/yaml'; import * as helmVersioning from '../../versioning/helm'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, ReleaseResult } from '../types'; -import { findSourceUrl } from './common'; -import type { HelmRepository, HelmRepositoryData } from './types'; +import type { HelmRepositoryData } from './schema'; +import { HelmRepositorySchema } from './schema'; export class HelmDatasource extends Datasource { static readonly id = 'helm'; @@ -34,63 +31,22 @@ export class HelmDatasource extends Datasource { @cache({ namespace: `datasource-${HelmDatasource.id}`, - key: (helmRepository: string) => helmRepository, + key: (helmRepository: string) => `repository-data:${helmRepository}`, }) - async getRepositoryData( - helmRepository: string, - ): Promise { - let res: HttpResponse; - try { - res = await this.http.get('index.yaml', { - baseUrl: ensureTrailingSlash(helmRepository), - }); - if (!res?.body) { - logger.warn( - { helmRepository }, - `Received invalid response from helm repository`, - ); - return null; - } - } catch (err) { + async getRepositoryData(helmRepository: string): Promise { + const { val, err } = await this.http + .getYamlSafe( + 'index.yaml', + { baseUrl: ensureTrailingSlash(helmRepository) }, + HelmRepositorySchema, + ) + .unwrap(); + + if (err) { this.handleGenericErrors(err); } - try { - // TODO: use schema (#9610) - const doc = parseSingleYaml(res.body); - if (!is.plainObject(doc)) { - logger.warn( - { helmRepository }, - `Failed to parse index.yaml from helm repository`, - ); - return null; - } - const result: HelmRepositoryData = {}; - for (const [name, releases] of Object.entries(doc.entries)) { - if (releases.length === 0) { - continue; - } - const latestRelease = releases[0]; - const sourceUrl = findSourceUrl(latestRelease); - result[name] = { - homepage: latestRelease.home, - sourceUrl, - releases: releases.map((release) => ({ - version: release.version, - releaseTimestamp: release.created ?? null, - // The Helm repository at Gitlab does not include a digest (#24280) - newDigest: release.digest ?? undefined, - })), - }; - } - return result; - } catch (err) { - logger.debug( - { helmRepository, err }, - `Failed to parse index.yaml from helm repository`, - ); - return null; - } + return val; } async getReleases({ @@ -103,10 +59,6 @@ export class HelmDatasource extends Datasource { } const repositoryData = await this.getRepositoryData(helmRepository); - if (!repositoryData) { - logger.debug(`Missing repo data from ${helmRepository}`); - return null; - } const releases = repositoryData[packageName]; if (!releases) { logger.debug( diff --git a/lib/modules/datasource/helm/schema.spec.ts b/lib/modules/datasource/helm/schema.spec.ts new file mode 100644 index 00000000000000..52f10871fb436f --- /dev/null +++ b/lib/modules/datasource/helm/schema.spec.ts @@ -0,0 +1,41 @@ +import { Fixtures } from '../../../../test/fixtures'; +import { Yaml } from '../../../util/schema-utils'; +import { HelmRepositorySchema } from './schema'; + +describe('modules/datasource/helm/schema', () => { + describe('sourceUrl', () => { + it('works', () => { + const repo = Yaml.pipe(HelmRepositorySchema).parse( + Fixtures.get('sample.yaml'), + ); + expect(repo).toMatchObject({ + airflow: { + homepage: + 'https://github.com/bitnami/charts/tree/master/bitnami/airflow', + sourceUrl: + 'https://github.com/bitnami/charts/tree/master/bitnami/airflow', + }, + coredns: { + homepage: 'https://coredns.io', + sourceUrl: 'https://github.com/coredns/helm', + }, + pgadmin4: { + homepage: 'https://www.pgadmin.org/', + sourceUrl: 'https://github.com/rowanruseler/helm-charts', + }, + 'private-chart-github': { + homepage: + 'https://github.example.com/some-org/charts/tree/master/private-chart', + sourceUrl: + 'https://github.example.com/some-org/charts/tree/master/private-chart', + }, + 'private-chart-gitlab': { + homepage: + 'https://gitlab.example.com/some/group/charts/-/tree/master/private-chart', + sourceUrl: + 'https://gitlab.example.com/some/group/charts/-/tree/master/private-chart', + }, + }); + }); + }); +}); diff --git a/lib/modules/datasource/helm/schema.ts b/lib/modules/datasource/helm/schema.ts new file mode 100644 index 00000000000000..64465295c67d86 --- /dev/null +++ b/lib/modules/datasource/helm/schema.ts @@ -0,0 +1,83 @@ +import { z } from 'zod'; +import { detectPlatform } from '../../../util/common'; +import { parseGitUrl } from '../../../util/git/url'; +import { regEx } from '../../../util/regex'; +import { LooseRecord } from '../../../util/schema-utils'; +import { MaybeTimestamp } from '../../../util/timestamp'; +import type { Release } from '../types'; + +const HelmReleaseSchema = z.object({ + version: z.string(), + created: MaybeTimestamp, + digest: z.string().optional().catch(undefined), + home: z.string().optional().catch(undefined), + sources: z.array(z.string()).catch([]), + urls: z.array(z.string()).catch([]), +}); +type HelmRelease = z.infer; + +const chartRepo = regEx(/charts?|helm|helm-charts/i); + +function isPossibleChartRepo(url: string): boolean { + if (detectPlatform(url) === null) { + return false; + } + + const parsed = parseGitUrl(url); + return chartRepo.test(parsed.name); +} + +const githubRelease = regEx( + /^(https:\/\/github\.com\/[^/]+\/[^/]+)\/releases\//, +); + +function getSourceUrl(release: HelmRelease): string | undefined { + // it's a github release :) + const [githubUrl] = release.urls; + const releaseMatch = githubRelease.exec(githubUrl); + if (releaseMatch) { + return releaseMatch[1]; + } + + if (release.home && isPossibleChartRepo(release.home)) { + return release.home; + } + + for (const url of release.sources) { + if (isPossibleChartRepo(url)) { + return url; + } + } + + // fallback + return release.sources[0]; +} + +export const HelmRepositorySchema = z + .object({ + entries: LooseRecord( + z.string(), + HelmReleaseSchema.array() + .min(1) + .transform((helmReleases) => { + const latestRelease = helmReleases[0]; + const homepage = latestRelease.home; + const sourceUrl = getSourceUrl(latestRelease); + const releases = helmReleases.map( + ({ + version, + created: releaseTimestamp, + digest: newDigest, + }): Release => ({ + version, + releaseTimestamp, + newDigest, + }), + ); + return { homepage, sourceUrl, releases }; + }), + ), + }) + .transform(({ entries }) => entries); + +export type HelmRepositoryData = z.infer; diff --git a/lib/modules/datasource/helm/types.ts b/lib/modules/datasource/helm/types.ts deleted file mode 100644 index 3c89e124e6f589..00000000000000 --- a/lib/modules/datasource/helm/types.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { ReleaseResult } from '../types'; - -export interface HelmRelease { - home?: string; - sources?: string[]; - version: string; - created: string; - digest: string | null; - urls: string[]; -} - -export interface HelmRepository { - entries: Record; -} - -export type HelmRepositoryData = Record; diff --git a/lib/modules/datasource/hermit/index.ts b/lib/modules/datasource/hermit/index.ts index 5e6b53bd4bcd2e..8a920b3c3d6758 100644 --- a/lib/modules/datasource/hermit/index.ts +++ b/lib/modules/datasource/hermit/index.ts @@ -125,7 +125,7 @@ export class HermitDatasource extends Datasource { const apiBaseUrl = getApiBaseUrl(`https://${host}`); - const indexRelease = await this.http.getJson( + const indexRelease = await this.http.getJsonUnchecked( `${apiBaseUrl}repos/${owner}/${repo}/releases/tags/index`, ); diff --git a/lib/modules/datasource/hex/index.ts b/lib/modules/datasource/hex/index.ts index ebf03fbfb47089..35c63c0802f6e2 100644 --- a/lib/modules/datasource/hex/index.ts +++ b/lib/modules/datasource/hex/index.ts @@ -55,7 +55,10 @@ export class HexDatasource extends Datasource { const { val: result, err } = await this.http .getJsonSafe(hexUrl, HexRelease) .onError((err) => { - logger.warn({ datasource: 'hex', packageName, err }, `Error fetching ${hexUrl}`); // prettier-ignore + logger.warn( + { url: hexUrl, datasource: 'hex', packageName, err }, + 'Error fetching from url', + ); }) .unwrap(); diff --git a/lib/modules/datasource/hex/schema.ts b/lib/modules/datasource/hex/schema.ts index a04aa81a49f399..be37b11f93c575 100644 --- a/lib/modules/datasource/hex/schema.ts +++ b/lib/modules/datasource/hex/schema.ts @@ -1,6 +1,7 @@ import is from '@sindresorhus/is'; import { z } from 'zod'; import { LooseArray } from '../../../util/schema-utils'; +import { MaybeTimestamp } from '../../../util/timestamp'; import type { Release, ReleaseResult } from '../types'; export const HexRelease = z @@ -29,7 +30,7 @@ export const HexRelease = z releases: LooseArray( z.object({ version: z.string(), - inserted_at: z.string().optional(), + inserted_at: MaybeTimestamp, }), ).refine((releases) => releases.length > 0, 'No releases found'), retirements: z diff --git a/lib/modules/datasource/hexpm-bob/index.ts b/lib/modules/datasource/hexpm-bob/index.ts index c841f26e9a38d4..817df0a53aebcb 100644 --- a/lib/modules/datasource/hexpm-bob/index.ts +++ b/lib/modules/datasource/hexpm-bob/index.ts @@ -3,6 +3,7 @@ import { logger } from '../../../logger'; import { ExternalHostError } from '../../../types/errors/external-host-error'; import { cache } from '../../../util/cache/package/decorator'; import { HttpError } from '../../../util/http'; +import { asTimestamp } from '../../../util/timestamp'; import { id as semverId } from '../../versioning/semver'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, ReleaseResult } from '../types'; @@ -69,7 +70,7 @@ export class HexpmBobDatasource extends Datasource { return { gitRef, isStable: HexpmBobDatasource.isStable(version, packageType), - releaseTimestamp: buildDate, + releaseTimestamp: asTimestamp(buildDate), version: HexpmBobDatasource.cleanVersion(version, packageType), }; }); diff --git a/lib/modules/datasource/index.ts b/lib/modules/datasource/index.ts index 8fa5355a125ceb..fec86165a9c690 100644 --- a/lib/modules/datasource/index.ts +++ b/lib/modules/datasource/index.ts @@ -287,7 +287,7 @@ async function fetchReleases( let { registryUrls } = config; // istanbul ignore if: need test if (!datasourceName || getDatasourceFor(datasourceName) === undefined) { - logger.warn('Unknown datasource: ' + datasourceName); + logger.warn({ datasource: datasourceName }, 'Unknown datasource'); return null; } if (datasourceName === 'npm') { diff --git a/lib/modules/datasource/java-version/index.ts b/lib/modules/datasource/java-version/index.ts index 1e1d68d4a2d7e9..4924b0c8ffb317 100644 --- a/lib/modules/datasource/java-version/index.ts +++ b/lib/modules/datasource/java-version/index.ts @@ -31,7 +31,8 @@ export class JavaVersionDatasource extends Datasource { ): Promise { const pgUrl = `${url}&page=${page}`; try { - const pgRes = await this.http.getJson(pgUrl); + const pgRes = + await this.http.getJsonUnchecked(pgUrl); return ( pgRes?.body?.versions?.map(({ semver }) => ({ version: semver, diff --git a/lib/modules/datasource/jenkins-plugins/index.spec.ts b/lib/modules/datasource/jenkins-plugins/index.spec.ts index 65cb05070d7e94..d8bee66fb89b6b 100644 --- a/lib/modules/datasource/jenkins-plugins/index.spec.ts +++ b/lib/modules/datasource/jenkins-plugins/index.spec.ts @@ -1,5 +1,6 @@ import { getPkgReleases } from '..'; import * as httpMock from '../../../../test/http-mock'; +import type { Timestamp } from '../../../util/timestamp'; import * as versioning from '../../versioning/docker'; import type { JenkinsPluginsInfoResponse, @@ -31,7 +32,7 @@ const jenkinsPluginsVersions: JenkinsPluginsVersionsResponse = { '3.0.0': { version: '3.0.0', url: 'https://download.example.com', - releaseTimestamp: '2020-05-13T00:11:40.00Z', + releaseTimestamp: '2020-05-13T00:11:40.00Z' as Timestamp, requiredCore: '2.164.3', }, }, @@ -87,12 +88,12 @@ describe('modules/datasource/jenkins-plugins/index', () => { }, { downloadUrl: 'https://download.example.com', - releaseTimestamp: '2020-01-02T00:00:00.000Z', + releaseTimestamp: '2020-01-02T00:00:00.000Z' as Timestamp, version: '2.0.0', }, { downloadUrl: 'https://download.example.com', - releaseTimestamp: '2020-05-13T00:11:40.000Z', + releaseTimestamp: '2020-05-13T00:11:40.000Z' as Timestamp, version: '3.0.0', }, ], @@ -155,12 +156,12 @@ describe('modules/datasource/jenkins-plugins/index', () => { }, { downloadUrl: 'https://download.example.com', - releaseTimestamp: '2020-01-02T00:00:00.000Z', + releaseTimestamp: '2020-01-02T00:00:00.000Z' as Timestamp, version: '2.0.0', }, { downloadUrl: 'https://download.example.com', - releaseTimestamp: '2020-05-13T00:11:40.000Z', + releaseTimestamp: '2020-05-13T00:11:40.000Z' as Timestamp, version: '3.0.0', }, ], diff --git a/lib/modules/datasource/jenkins-plugins/index.ts b/lib/modules/datasource/jenkins-plugins/index.ts index db434ba666c26c..02fa74fdf60254 100644 --- a/lib/modules/datasource/jenkins-plugins/index.ts +++ b/lib/modules/datasource/jenkins-plugins/index.ts @@ -1,6 +1,7 @@ import { logger } from '../../../logger'; import { cache } from '../../../util/cache/package/decorator'; import { clone } from '../../../util/clone'; +import { asTimestamp } from '../../../util/timestamp'; import { ensureTrailingSlash } from '../../../util/url'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; @@ -96,8 +97,7 @@ export class JenkinsPluginsDatasource extends Datasource { const downloadUrl = plugins[name][version]?.url; const buildDate = plugins[name][version]?.buildDate; const releaseTimestamp = - plugins[name][version]?.releaseTimestamp ?? - (buildDate ? new Date(`${buildDate} UTC`).toISOString() : null); + plugins[name][version]?.releaseTimestamp ?? asTimestamp(buildDate); const jenkins = plugins[name][version]?.requiredCore; const constraints = jenkins ? { jenkins: [`>=${jenkins}`] } : undefined; return { @@ -117,7 +117,7 @@ export class JenkinsPluginsDatasource extends Datasource { try { logger.debug(`jenkins-plugins: Fetching Jenkins plugins from ${url}`); const startTime = Date.now(); - response = (await this.http.getJson(url)).body; + response = (await this.http.getJsonUnchecked(url)).body; const durationMs = Math.round(Date.now() - startTime); logger.debug( { durationMs }, diff --git a/lib/modules/datasource/jenkins-plugins/types.ts b/lib/modules/datasource/jenkins-plugins/types.ts index 5224ff1072240d..b8e350bebc5927 100644 --- a/lib/modules/datasource/jenkins-plugins/types.ts +++ b/lib/modules/datasource/jenkins-plugins/types.ts @@ -1,3 +1,5 @@ +import type { Timestamp } from '../../../util/timestamp'; + export interface JenkinsPluginInfo { name: string; scm?: string; @@ -9,7 +11,7 @@ export interface JenkinsPluginVersion { url?: string; requiredCore?: string; - releaseTimestamp?: string; + releaseTimestamp?: Timestamp; } export interface JenkinsPluginsInfoResponse { diff --git a/lib/modules/datasource/maven/index.spec.ts b/lib/modules/datasource/maven/index.spec.ts index 4a87d5323f416e..d8d903184d6816 100644 --- a/lib/modules/datasource/maven/index.spec.ts +++ b/lib/modules/datasource/maven/index.spec.ts @@ -1,5 +1,6 @@ import { HeadObjectCommand, S3Client } from '@aws-sdk/client-s3'; import { mockClient } from 'aws-sdk-client-mock'; +import { codeBlock } from 'common-tags'; import { GoogleAuth as _googleAuth } from 'google-auth-library'; import { DateTime } from 'luxon'; import type { Release, ReleaseResult } from '..'; @@ -315,6 +316,72 @@ describe('modules/datasource/maven/index', () => { expect(res?.sourceUrl).toBe('https://github.com/example/test'); }); + describe('supports relocation', () => { + it('with only groupId present', async () => { + const pom = codeBlock` + + + + io.example + + + + `; + mockGenericPackage({ pom }); + + const res = await get(); + + expect(res).toMatchObject({ + replacementName: 'io.example:package', + replacementVersion: '2.0.0', + }); + }); + + it('with only artifactId present', async () => { + const pom = codeBlock` + + + + foo + + + + `; + mockGenericPackage({ pom }); + + const res = await get(); + + expect(res).toMatchObject({ + replacementName: 'org.example:foo', + replacementVersion: '2.0.0', + }); + }); + + it('with all elments present', async () => { + const pom = codeBlock` + + + + io.example + foo + 1.2.3 + test relocation + + + + `; + mockGenericPackage({ pom }); + + const res = await get(); + + expect(res).toMatchObject({ + replacementName: 'io.example:foo', + replacementVersion: '1.2.3', + deprecationMessage: 'test relocation', + }); + }); + }); + it('removes authentication header after redirect', async () => { const frontendHost = 'frontend_for_private_s3_repository'; const frontendUrl = `https://${frontendHost}/maven2`; diff --git a/lib/modules/datasource/maven/index.ts b/lib/modules/datasource/maven/index.ts index b35bc6996a59bb..32ac272918fe57 100644 --- a/lib/modules/datasource/maven/index.ts +++ b/lib/modules/datasource/maven/index.ts @@ -4,6 +4,7 @@ import { GlobalConfig } from '../../../config/global'; import { logger } from '../../../logger'; import * as packageCache from '../../../util/cache/package'; import { cache } from '../../../util/cache/package/decorator'; +import { asTimestamp } from '../../../util/timestamp'; import { ensureTrailingSlash } from '../../../util/url'; import mavenVersion from '../../versioning/maven'; import * as mavenVersioning from '../../versioning/maven'; @@ -198,7 +199,7 @@ export class MavenDatasource extends Datasource { } if (is.date(res)) { - release.releaseTimestamp = res.toISOString(); + release.releaseTimestamp = asTimestamp(res.toISOString()); } return release; diff --git a/lib/modules/datasource/maven/types.ts b/lib/modules/datasource/maven/types.ts index 853326887eaadc..f367802cd21735 100644 --- a/lib/modules/datasource/maven/types.ts +++ b/lib/modules/datasource/maven/types.ts @@ -18,7 +18,12 @@ export type HttpResourceCheckResult = 'found' | 'not-found' | 'error' | Date; export type DependencyInfo = Pick< ReleaseResult, - 'homepage' | 'sourceUrl' | 'packageScope' + | 'homepage' + | 'sourceUrl' + | 'packageScope' + | 'replacementName' + | 'replacementVersion' + | 'deprecationMessage' >; export interface MavenFetchSuccess { diff --git a/lib/modules/datasource/maven/util.ts b/lib/modules/datasource/maven/util.ts index 1edc357068e5cd..696836fd41f913 100644 --- a/lib/modules/datasource/maven/util.ts +++ b/lib/modules/datasource/maven/util.ts @@ -12,8 +12,8 @@ import { Result } from '../../../util/result'; import type { S3UrlParts } from '../../../util/s3'; import { getS3Client, parseS3Url } from '../../../util/s3'; import { streamToString } from '../../../util/streams'; +import { asTimestamp } from '../../../util/timestamp'; import { ensureTrailingSlash, parseUrl } from '../../../util/url'; -import { normalizeDate } from '../metadata'; import { getGoogleAuthToken } from '../util'; import { MAVEN_REPO } from './common'; import type { @@ -83,7 +83,7 @@ export async function downloadHttpProtocol( result.isCacheable = true; } - const lastModified = normalizeDate(res?.headers?.['last-modified']); + const lastModified = asTimestamp(res?.headers?.['last-modified']); if (lastModified) { result.lastModified = lastModified; } @@ -203,7 +203,7 @@ export async function downloadS3Protocol( const data = await streamToString(Body); const result: MavenFetchSuccess = { data }; - const lastModified = normalizeDate(LastModified); + const lastModified = asTimestamp(LastModified); if (lastModified) { result.lastModified = lastModified; } @@ -276,7 +276,7 @@ async function checkHttpResource( const res = await http.head(pkgUrl.toString()); const timestamp = res?.headers?.['last-modified']; if (timestamp) { - const isoTimestamp = normalizeDate(timestamp); + const isoTimestamp = asTimestamp(timestamp); if (isoTimestamp) { const releaseDate = DateTime.fromISO(isoTimestamp, { zone: 'UTC', @@ -543,6 +543,23 @@ export async function getDependencyInfo( } } + const relocation = pomContent.descendantWithPath( + 'distributionManagement.relocation', + ); + if (relocation) { + const relocationGroup = + relocation.valueWithPath('groupId') ?? dependency.group; + const relocationName = + relocation.valueWithPath('artifactId') ?? dependency.name; + result.replacementName = `${relocationGroup}:${relocationName}`; + const relocationVersion = relocation.valueWithPath('version'); + result.replacementVersion = relocationVersion ?? version; + const relocationMessage = relocation.valueWithPath('message'); + if (relocationMessage) { + result.deprecationMessage = relocationMessage; + } + } + const groupId = pomContent.valueWithPath('groupId'); if (groupId) { result.packageScope = groupId; diff --git a/lib/modules/datasource/metadata.spec.ts b/lib/modules/datasource/metadata.spec.ts index 0b49c803ce8db8..87a73eb8c65ada 100644 --- a/lib/modules/datasource/metadata.spec.ts +++ b/lib/modules/datasource/metadata.spec.ts @@ -1,11 +1,11 @@ import { partial } from '../../../test/util'; +import type { Timestamp } from '../../util/timestamp'; import { HelmDatasource } from './helm'; import { MavenDatasource } from './maven'; import { addMetaData, massageGithubUrl, massageUrl, - normalizeDate, shouldDeleteHomepage, } from './metadata'; import { NpmDatasource } from './npm'; @@ -16,13 +16,22 @@ describe('modules/datasource/metadata', () => { it('Should handle manualChangelogUrls', () => { const dep: ReleaseResult = { releases: [ - { version: '2.0.0', releaseTimestamp: '2018-07-13T10:14:17.000Z' }, + { + version: '2.0.0', + releaseTimestamp: '2018-07-13T10:14:17.000Z' as Timestamp, + }, { version: '2.0.0.dev1', - releaseTimestamp: '2017-10-24T10:09:16.000Z', + releaseTimestamp: '2017-10-24T10:09:16.000Z' as Timestamp, + }, + { + version: '2.1.0', + releaseTimestamp: '2019-01-20T19:59:28.000Z' as Timestamp, + }, + { + version: '2.2.0', + releaseTimestamp: '2019-07-16T18:29:00.000Z' as Timestamp, }, - { version: '2.1.0', releaseTimestamp: '2019-01-20T19:59:28.000Z' }, - { version: '2.2.0', releaseTimestamp: '2019-07-16T18:29:00.000Z' }, ], }; @@ -39,13 +48,22 @@ describe('modules/datasource/metadata', () => { it('Should handle manualSourceUrls', () => { const dep: ReleaseResult = { releases: [ - { version: '2.0.0', releaseTimestamp: '2018-07-13T10:14:17.000Z' }, + { + version: '2.0.0', + releaseTimestamp: '2018-07-13T10:14:17.000Z' as Timestamp, + }, { version: '2.0.0.dev1', - releaseTimestamp: '2017-10-24T10:09:16.000Z', + releaseTimestamp: '2017-10-24T10:09:16.000Z' as Timestamp, + }, + { + version: '2.1.0', + releaseTimestamp: '2019-01-20T19:59:28.000Z' as Timestamp, + }, + { + version: '2.2.0', + releaseTimestamp: '2019-07-16T18:29:00.000Z' as Timestamp, }, - { version: '2.1.0', releaseTimestamp: '2019-01-20T19:59:28.000Z' }, - { version: '2.2.0', releaseTimestamp: '2019-07-16T18:29:00.000Z' }, ], }; @@ -62,13 +80,22 @@ describe('modules/datasource/metadata', () => { const dep: ReleaseResult = { sourceUrl: 'https://github.com/carltongibson/django-filter/tree/master', releases: [ - { version: '2.0.0', releaseTimestamp: '2018-07-13T10:14:17.000Z' }, + { + version: '2.0.0', + releaseTimestamp: '2018-07-13T10:14:17.000Z' as Timestamp, + }, { version: '2.0.0.dev1', - releaseTimestamp: '2017-10-24T10:09:16.000Z', + releaseTimestamp: '2017-10-24T10:09:16.000Z' as Timestamp, + }, + { + version: '2.1.0', + releaseTimestamp: '2019-01-20T19:59:28.000Z' as Timestamp, + }, + { + version: '2.2.0', + releaseTimestamp: '2019-07-16T18:29:00.000Z' as Timestamp, }, - { version: '2.1.0', releaseTimestamp: '2019-01-20T19:59:28.000Z' }, - { version: '2.2.0', releaseTimestamp: '2019-07-16T18:29:00.000Z' }, ], }; const datasource = PypiDatasource.id; @@ -143,13 +170,22 @@ describe('modules/datasource/metadata', () => { const dep: ReleaseResult = { sourceUrl: 'https://some.github.com/repo', releases: [ - { version: '2.0.0', releaseTimestamp: '2018-07-13T10:14:17.000Z' }, + { + version: '2.0.0', + releaseTimestamp: '2018-07-13T10:14:17.000Z' as Timestamp, + }, { version: '2.0.0.dev1', - releaseTimestamp: '2017-10-24T10:09:16.000Z', + releaseTimestamp: '2017-10-24T10:09:16.000Z' as Timestamp, + }, + { + version: '2.1.0', + releaseTimestamp: '2019-01-20T19:59:28.000Z' as Timestamp, + }, + { + version: '2.2.0', + releaseTimestamp: '2019-07-16T18:29:00.000Z' as Timestamp, }, - { version: '2.1.0', releaseTimestamp: '2019-01-20T19:59:28.000Z' }, - { version: '2.2.0', releaseTimestamp: '2019-07-16T18:29:00.000Z' }, ], }; const datasource = PypiDatasource.id; @@ -165,10 +201,13 @@ describe('modules/datasource/metadata', () => { const dep: ReleaseResult = { sourceUrl: 'https://gitlab.com/meno/dropzone/tree/master', releases: [ - { version: '5.7.0', releaseTimestamp: '2020-02-14T13:12:00.000Z' }, + { + version: '5.7.0', + releaseTimestamp: '2020-02-14T13:12:00.000Z' as Timestamp, + }, { version: '5.6.1', - releaseTimestamp: '2020-02-14T10:04:00.000Z', + releaseTimestamp: '2020-02-14T10:04:00.000Z' as Timestamp, }, ], }; @@ -185,10 +224,13 @@ describe('modules/datasource/metadata', () => { const dep = { sourceUrl: 'https://gitlab-nope', releases: [ - { version: '5.7.0', releaseTimestamp: '2020-02-14T13:12:00.000Z' }, + { + version: '5.7.0', + releaseTimestamp: '2020-02-14T13:12:00.000Z' as Timestamp, + }, { version: '5.6.1', - releaseTimestamp: '2020-02-14T10:04:00.000Z', + releaseTimestamp: '2020-02-14T10:04:00.000Z' as Timestamp, }, ], }; @@ -205,10 +247,13 @@ describe('modules/datasource/metadata', () => { const dep = { sourceUrl: 'https://nope-nope-nope', releases: [ - { version: '5.7.0', releaseTimestamp: '2020-02-14T13:12:00.000Z' }, + { + version: '5.7.0', + releaseTimestamp: '2020-02-14T13:12:00.000Z' as Timestamp, + }, { version: '5.6.1', - releaseTimestamp: '2020-02-14T10:04:00.000Z', + releaseTimestamp: '2020-02-14T10:04:00.000Z' as Timestamp, }, ], }; @@ -225,10 +270,13 @@ describe('modules/datasource/metadata', () => { const dep = { sourceUrl: 'not-a-url', releases: [ - { version: '5.7.0', releaseTimestamp: '2020-02-14T13:12:00.000Z' }, + { + version: '5.7.0', + releaseTimestamp: '2020-02-14T13:12:00.000Z' as Timestamp, + }, { version: '5.6.1', - releaseTimestamp: '2020-02-14T10:04:00.000Z', + releaseTimestamp: '2020-02-14T10:04:00.000Z' as Timestamp, }, ], }; @@ -281,10 +329,19 @@ describe('modules/datasource/metadata', () => { it('Should normalize releaseTimestamp', () => { const dep = { releases: [ - { version: '1.0.1', releaseTimestamp: '2000-01-01T12:34:56' }, - { version: '1.0.2', releaseTimestamp: '2000-01-02T12:34:56.000Z' }, - { version: '1.0.3', releaseTimestamp: '2000-01-03T14:34:56.000+02:00' }, - { version: '1.0.4', releaseTimestamp: '20000103150210' }, + { + version: '1.0.1', + releaseTimestamp: '2000-01-01T12:34:56' as Timestamp, + }, + { + version: '1.0.2', + releaseTimestamp: '2000-01-02T12:34:56.000Z' as Timestamp, + }, + { + version: '1.0.3', + releaseTimestamp: '2000-01-03T14:34:56.000+02:00' as Timestamp, + }, + { version: '1.0.4', releaseTimestamp: '20000103150210' as Timestamp }, ], }; addMetaData(dep, MavenDatasource.id, 'foobar'); @@ -375,9 +432,18 @@ describe('modules/datasource/metadata', () => { homepage: 'https://github.com/foo/bar', sourceUrl: 'https://github.com/foo/bar', releases: [ - { version: '1.0.1', releaseTimestamp: '2000-01-01T12:34:56' }, - { version: '1.0.2', releaseTimestamp: '2000-01-02T12:34:56.000Z' }, - { version: '1.0.3', releaseTimestamp: '2000-01-03T14:34:56.000+02:00' }, + { + version: '1.0.1', + releaseTimestamp: '2000-01-01T12:34:56' as Timestamp, + }, + { + version: '1.0.2', + releaseTimestamp: '2000-01-02T12:34:56.000Z' as Timestamp, + }, + { + version: '1.0.3', + releaseTimestamp: '2000-01-03T14:34:56.000+02:00' as Timestamp, + }, ], }; addMetaData(dep, MavenDatasource.id, 'foobar'); @@ -405,9 +471,18 @@ describe('modules/datasource/metadata', () => { sourceUrl: 'https://gitlab.com/meno/repo', homepage: 'https://gitlab.com/meno/repo', releases: [ - { version: '1.0.1', releaseTimestamp: '2000-01-01T12:34:56' }, - { version: '1.0.2', releaseTimestamp: '2000-01-02T12:34:56.000Z' }, - { version: '1.0.3', releaseTimestamp: '2000-01-03T14:34:56.000+02:00' }, + { + version: '1.0.1', + releaseTimestamp: '2000-01-01T12:34:56' as Timestamp, + }, + { + version: '1.0.2', + releaseTimestamp: '2000-01-02T12:34:56.000Z' as Timestamp, + }, + { + version: '1.0.3', + releaseTimestamp: '2000-01-03T14:34:56.000+02:00' as Timestamp, + }, ], }; addMetaData(dep, MavenDatasource.id, 'foobar'); @@ -434,9 +509,18 @@ describe('modules/datasource/metadata', () => { const dep = { sourceUrl: 'https://gitlab.com/meno/repo', releases: [ - { version: '1.0.1', releaseTimestamp: '2000-01-01T12:34:56' }, - { version: '1.0.2', releaseTimestamp: '2000-01-02T12:34:56.000Z' }, - { version: '1.0.3', releaseTimestamp: '2000-01-03T14:34:56.000+02:00' }, + { + version: '1.0.1', + releaseTimestamp: '2000-01-01T12:34:56' as Timestamp, + }, + { + version: '1.0.2', + releaseTimestamp: '2000-01-02T12:34:56.000Z' as Timestamp, + }, + { + version: '1.0.3', + releaseTimestamp: '2000-01-03T14:34:56.000+02:00' as Timestamp, + }, ], }; addMetaData(dep, MavenDatasource.id, 'foobar'); @@ -463,9 +547,18 @@ describe('modules/datasource/metadata', () => { const dep = { homepage: 'https://somesource.com/', releases: [ - { version: '1.0.1', releaseTimestamp: '2000-01-01T12:34:56' }, - { version: '1.0.2', releaseTimestamp: '2000-01-02T12:34:56.000Z' }, - { version: '1.0.3', releaseTimestamp: '2000-01-03T14:34:56.000+02:00' }, + { + version: '1.0.1', + releaseTimestamp: '2000-01-01T12:34:56' as Timestamp, + }, + { + version: '1.0.2', + releaseTimestamp: '2000-01-02T12:34:56.000Z' as Timestamp, + }, + { + version: '1.0.3', + releaseTimestamp: '2000-01-03T14:34:56.000+02:00' as Timestamp, + }, ], }; addMetaData(dep, MavenDatasource.id, 'foobar'); @@ -521,23 +614,4 @@ describe('modules/datasource/metadata', () => { sourceUrl: 'https://github.com/flyingcircusio/pycountry', }); }); - - describe('normalizeDate()', () => { - it('works for number input', () => { - const now = Date.now(); - expect(normalizeDate(now)).toBe(new Date(now).toISOString()); - }); - - it('works for string input', () => { - expect(normalizeDate('2021-01-01')).toBe( - new Date('2021-01-01').toISOString(), - ); - }); - - it('works for Date instance', () => { - expect(normalizeDate(new Date('2021-01-01'))).toBe( - new Date('2021-01-01').toISOString(), - ); - }); - }); }); diff --git a/lib/modules/datasource/metadata.ts b/lib/modules/datasource/metadata.ts index 8f6fd5fc56f5cf..6fe5efa54a465a 100644 --- a/lib/modules/datasource/metadata.ts +++ b/lib/modules/datasource/metadata.ts @@ -1,10 +1,10 @@ import is from '@sindresorhus/is'; import parse from 'github-url-from-git'; -import { DateTime } from 'luxon'; import { detectPlatform } from '../../util/common'; import { parseGitUrl } from '../../util/git/url'; import * as hostRules from '../../util/host-rules'; import { regEx } from '../../util/regex'; +import { asTimestamp } from '../../util/timestamp'; import { isHttpUrl, parseUrl, trimTrailingSlash } from '../../util/url'; import { manualChangelogUrls, manualSourceUrls } from './metadata-manual'; import type { ReleaseResult } from './types'; @@ -64,56 +64,11 @@ function massageGitAtUrl(url: string): string { return massagedUrl; } -export function normalizeDate(input: any): string | null { - if ( - typeof input === 'number' && - !Number.isNaN(input) && - input > 0 && - input <= Date.now() + 24 * 60 * 60 * 1000 - ) { - return new Date(input).toISOString(); - } - - if (typeof input === 'string') { - // `Date.parse()` is more permissive, but it assumes local time zone - // for inputs like `2021-01-01`. - // - // Here we try to parse with default UTC with fallback to `Date.parse()`. - // - // It allows us not to care about machine timezones so much, though - // some misinterpretation is still possible, but only if both: - // - // 1. Renovate machine is configured for non-UTC zone - // 2. Format of `input` is very exotic - // (from `DateTime.fromISO()` perspective) - // - - let luxonDate = DateTime.fromISO(input, { zone: 'UTC' }); - if (luxonDate.isValid) { - return luxonDate.toISO(); - } - luxonDate = DateTime.fromFormat(input, 'yyyyMMddHHmmss', { - zone: 'UTC', - }); - if (luxonDate.isValid) { - return luxonDate.toISO(); - } - - return normalizeDate(Date.parse(input)); - } - - if (input instanceof Date) { - return input.toISOString(); - } - - return null; -} - function massageTimestamps(dep: ReleaseResult): void { for (const release of dep.releases || []) { let { releaseTimestamp } = release; delete release.releaseTimestamp; - releaseTimestamp = normalizeDate(releaseTimestamp); + releaseTimestamp = asTimestamp(releaseTimestamp); if (releaseTimestamp) { release.releaseTimestamp = releaseTimestamp; } diff --git a/lib/modules/datasource/node-version/index.ts b/lib/modules/datasource/node-version/index.ts index c37c707c32cabf..c5caf6d8ed4898 100644 --- a/lib/modules/datasource/node-version/index.ts +++ b/lib/modules/datasource/node-version/index.ts @@ -1,4 +1,5 @@ import { cache } from '../../../util/cache/package/decorator'; +import { asTimestamp } from '../../../util/timestamp'; import { joinUrlParts } from '../../../util/url'; import { id as versioning } from '../../versioning/node'; import { Datasource } from '../datasource'; @@ -46,14 +47,14 @@ export class NodeVersionDatasource extends Datasource { }; try { const resp = ( - await this.http.getJson( + await this.http.getJsonUnchecked( joinUrlParts(registryUrl, 'index.json'), ) ).body; result.releases.push( ...resp.map(({ version, date, lts }) => ({ version, - releaseTimestamp: date, + releaseTimestamp: asTimestamp(date), isStable: lts !== false, })), ); diff --git a/lib/modules/datasource/npm/get.ts b/lib/modules/datasource/npm/get.ts index cd8ea585ec259f..4bee6946abd0dc 100644 --- a/lib/modules/datasource/npm/get.ts +++ b/lib/modules/datasource/npm/get.ts @@ -12,6 +12,7 @@ import type { Http } from '../../../util/http'; import type { HttpOptions } from '../../../util/http/types'; import { regEx } from '../../../util/regex'; import { HttpCacheStats } from '../../../util/stats'; +import { asTimestamp } from '../../../util/timestamp'; import { joinUrlParts } from '../../../util/url'; import type { Release, ReleaseResult } from '../types'; import type { CachedReleaseResult, NpmResponse } from './types'; @@ -144,7 +145,7 @@ export async function getDependency( }); } - const raw = await http.getJson(packageUrl, options); + const raw = await http.getJsonUnchecked(packageUrl, options); if (cachedResult?.cacheData && raw.statusCode === 304) { logger.trace(`Cached npm result for ${packageName} is revalidated`); HttpCacheStats.incRemoteHits(packageUrl); @@ -199,8 +200,9 @@ export async function getDependency( dependencies: res.versions?.[version].dependencies, devDependencies: res.versions?.[version].devDependencies, }; - if (res.time?.[version]) { - release.releaseTimestamp = res.time[version]; + const releaseTimestamp = asTimestamp(res.time?.[version]); + if (releaseTimestamp) { + release.releaseTimestamp = releaseTimestamp; } if (res.versions?.[version].deprecated) { release.isDeprecated = true; diff --git a/lib/modules/datasource/npm/npmrc.ts b/lib/modules/datasource/npm/npmrc.ts index 086bb07338bfe5..5129f50ea82a8d 100644 --- a/lib/modules/datasource/npm/npmrc.ts +++ b/lib/modules/datasource/npm/npmrc.ts @@ -26,7 +26,7 @@ function envReplace(value: any, env = process.env): any { return value.replace(ENV_EXPR, (match, _esc, envVarName) => { if (env[envVarName] === undefined) { - logger.warn('Failed to replace env in config: ' + match); + logger.warn({ match }, 'Failed to replace env in config'); throw new Error('env-replace'); } return env[envVarName]; diff --git a/lib/modules/datasource/nuget/__snapshots__/index.spec.ts.snap b/lib/modules/datasource/nuget/__snapshots__/index.spec.ts.snap index e668b57267e71e..9b1702cc94ed93 100644 --- a/lib/modules/datasource/nuget/__snapshots__/index.spec.ts.snap +++ b/lib/modules/datasource/nuget/__snapshots__/index.spec.ts.snap @@ -34,7 +34,6 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v2) 1`] "version": "2.5.10.11092", }, { - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "2.6.0.12051", }, { @@ -228,7 +227,6 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) fee }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "2.6.0.12051", }, { @@ -418,7 +416,6 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) fee }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "2.6.0.12051", }, { @@ -604,12 +601,10 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) for }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "2.0.1", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "2.0.1.1", }, { @@ -638,7 +633,6 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) for }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.0.0-rc", }, { @@ -651,12 +645,10 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) for }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.1.0-alpha1", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.1.0-rc1", }, { @@ -665,7 +657,6 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) for }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.1.1-rc1", }, { @@ -678,7 +669,6 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) for }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.2.0-rc1", }, { @@ -687,7 +677,6 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) for }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.2.1-rc1", }, { @@ -704,52 +693,42 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) for }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.3.0-alpha1", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.3.0-alpha2", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.3.0-alpha3", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.3.0-alpha4", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.3.0-beta1", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.3.0-beta2", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.3.0-beta3", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.3.0-rc1", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.3.0-rc2", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.3.0-rc3", }, { @@ -758,7 +737,6 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) for }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.3.1-rc1", }, { @@ -779,7 +757,6 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) for }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.3.5-alpha1", }, { @@ -800,7 +777,6 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) for }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.3.9-test-retry-archive", }, { @@ -817,112 +793,90 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) for }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.0-alpha1", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.0-alpha2", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.0-alpha3", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.0-alpha4", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.0-beta-14", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.0-beta1", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.0-beta10", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.0-beta11", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.0-beta12", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.0-beta13", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.0-beta2", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.0-beta3", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.0-beta4", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.0-beta5", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.0-beta6", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.0-beta7", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.0-beta8", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.0-beta9", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.0-betaV14", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.0-betaV15", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.0-rc1", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.0-rc2", }, { @@ -931,12 +885,10 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) for }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.1-dev-b4084", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.1-dev-b4085", }, { @@ -945,12 +897,10 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) for }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.2-rc1", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.2-rc2", }, { @@ -967,7 +917,6 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) for }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.5-beta1", }, { @@ -976,22 +925,18 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) for }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.6-beta1", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.6-beta2", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.6-beta3", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.4.6-rc1", }, { @@ -1028,97 +973,78 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) for }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.5.0-alpha01", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.5.0-alpha02", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.5.0-alpha03", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.5.0-alpha04", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.5.0-beta01", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.5.0-beta02", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.5.0-beta03", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.5.0-beta04", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.5.0-beta05", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.5.0-beta06", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.5.0-beta07", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.5.0-beta08", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.5.0-rc01", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.5.0-rc02", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.5.0-rc03", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.5.0-rc04", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.5.0-rc05", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.5.0-rc06", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.5.0-rc07", }, { @@ -1171,17 +1097,14 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) for }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.6.0-rc1", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.6.0-rc2", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.6.0-rc3", }, { @@ -1222,7 +1145,6 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) for }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "4.7.0-rc1", }, { @@ -1243,67 +1165,54 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) for }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "5.0.0-beta01", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "5.0.0-beta02", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "5.0.0-beta03", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "5.0.0-beta03-tryoutMutex", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "5.0.0-beta04", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "5.0.0-beta05", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "5.0.0-beta05-test", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "5.0.0-beta06", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "5.0.0-beta07", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "5.0.0-beta08", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "5.0.0-beta09", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "5.0.0-beta10", }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "5.0.0-beta11", }, ], @@ -1329,7 +1238,6 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) nus }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "2.6.0.12051", }, { @@ -1519,7 +1427,6 @@ exports[`modules/datasource/nuget/index getReleases processes real data (v3) nus }, { "isDeprecated": true, - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "2.6.0.12051", }, { @@ -1868,7 +1775,6 @@ exports[`modules/datasource/nuget/index getReleases returns deduplicated results { "isDeprecated": true, "registryUrl": "https://api.nuget.org/v3/index.json", - "releaseTimestamp": "1900-01-01T00:00:00.000Z", "version": "2.6.0.12051", }, { diff --git a/lib/modules/datasource/nuget/v2.ts b/lib/modules/datasource/nuget/v2.ts index 48c89d34b4a6fd..b542298eadcb93 100644 --- a/lib/modules/datasource/nuget/v2.ts +++ b/lib/modules/datasource/nuget/v2.ts @@ -3,6 +3,7 @@ import { XmlDocument } from 'xmldoc'; import { logger } from '../../../logger'; import type { Http } from '../../../util/http'; import { regEx } from '../../../util/regex'; +import { asTimestamp } from '../../../util/timestamp'; import type { ReleaseResult } from '../types'; import { massageUrl, removeBuildMeta } from './common'; @@ -32,7 +33,9 @@ export class NugetV2Api { for (const pkgInfo of pkgInfoList) { const version = this.getPkgProp(pkgInfo, 'Version'); - const releaseTimestamp = this.getPkgProp(pkgInfo, 'Published'); + const releaseTimestamp = asTimestamp( + this.getPkgProp(pkgInfo, 'Published'), + ); dep.releases.push({ // TODO: types (#22198) version: removeBuildMeta(`${version}`), diff --git a/lib/modules/datasource/nuget/v3.ts b/lib/modules/datasource/nuget/v3.ts index 2e6e289bf0478a..2492c02d70a674 100644 --- a/lib/modules/datasource/nuget/v3.ts +++ b/lib/modules/datasource/nuget/v3.ts @@ -13,6 +13,7 @@ import type { Http } from '../../../util/http'; import { HttpError } from '../../../util/http'; import * as p from '../../../util/promises'; import { regEx } from '../../../util/regex'; +import { asTimestamp } from '../../../util/timestamp'; import { ensureTrailingSlash } from '../../../util/url'; import { api as versioning } from '../../versioning/nuget'; import type { Release, ReleaseResult } from '../types'; @@ -52,7 +53,8 @@ export class NugetV3Api { ); // istanbul ignore else: currently not testable if (!servicesIndexRaw) { - servicesIndexRaw = (await http.getJson(url)).body; + servicesIndexRaw = (await http.getJsonUnchecked(url)) + .body; await packageCache.set( NugetV3Api.cacheNamespace, responseCacheKey, @@ -132,7 +134,7 @@ export class NugetV3Api { let items = catalogPage.items; if (!items) { const url = catalogPage['@id']; - const catalogPageFull = await http.getJson(url); + const catalogPageFull = await http.getJsonUnchecked(url); items = catalogPageFull.body.items; } return items.map(({ catalogEntry }) => catalogEntry); @@ -146,7 +148,8 @@ export class NugetV3Api { ): Promise { const baseUrl = feedUrl.replace(regEx(/\/*$/), ''); const url = `${baseUrl}/${pkgName.toLowerCase()}/index.json`; - const packageRegistration = await http.getJson(url); + const packageRegistration = + await http.getJsonUnchecked(url); const catalogPages = packageRegistration.body.items || []; const catalogPagesQueue = catalogPages.map( (page) => (): Promise => this.getCatalogEntry(http, page), @@ -159,14 +162,9 @@ export class NugetV3Api { let latestStable: string | null = null; let nupkgUrl: string | null = null; const releases = catalogEntries.map( - ({ - version, - published: releaseTimestamp, - projectUrl, - listed, - packageContent, - }) => { + ({ version, published, projectUrl, listed, packageContent }) => { const release: Release = { version: removeBuildMeta(version) }; + const releaseTimestamp = asTimestamp(published); if (releaseTimestamp) { release.releaseTimestamp = releaseTimestamp; } diff --git a/lib/modules/datasource/orb/index.ts b/lib/modules/datasource/orb/index.ts index 80fa61e4e93204..0de66700aee4fc 100644 --- a/lib/modules/datasource/orb/index.ts +++ b/lib/modules/datasource/orb/index.ts @@ -1,5 +1,6 @@ import { logger } from '../../../logger'; import { cache } from '../../../util/cache/package/decorator'; +import { asTimestamp } from '../../../util/timestamp'; import { joinUrlParts } from '../../../util/url'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, ReleaseResult } from '../types'; @@ -71,7 +72,7 @@ export class OrbDatasource extends Datasource { : `https://circleci.com/developer/orbs/orb/${packageName}`; const releases = orb.versions.map(({ version, createdAt }) => ({ version, - releaseTimestamp: createdAt ?? null, + releaseTimestamp: asTimestamp(createdAt), })); const dep = { homepage, isPrivate: !!orb.isPrivate, releases }; diff --git a/lib/modules/datasource/packagist/index.ts b/lib/modules/datasource/packagist/index.ts index 1284745a7efdfa..da8573de0bfcd5 100644 --- a/lib/modules/datasource/packagist/index.ts +++ b/lib/modules/datasource/packagist/index.ts @@ -49,13 +49,13 @@ export class PackagistDatasource extends Datasource { return username && password ? { username, password } : {}; } - private async getJson>( + private async getJson>( url: string, - schema: U, - ): Promise> { + schema: Schema, + ): Promise> { const opts = PackagistDatasource.getHostOpts(url); - const { body } = await this.http.getJson(url, opts); - return schema.parse(body); + const { body } = await this.http.getJson(url, opts, schema); + return body; } @cache({ diff --git a/lib/modules/datasource/packagist/schema.spec.ts b/lib/modules/datasource/packagist/schema.spec.ts index a83918d124f34e..6f8b759ded1670 100644 --- a/lib/modules/datasource/packagist/schema.spec.ts +++ b/lib/modules/datasource/packagist/schema.spec.ts @@ -1,3 +1,4 @@ +import type { Timestamp } from '../../../util/timestamp'; import type { ReleaseResult } from '../types'; import { ComposerRelease, @@ -144,10 +145,13 @@ describe('modules/datasource/packagist/schema', () => { }); expect( - ComposerRelease.parse({ version: '1.2.3', time: '12345' }), + ComposerRelease.parse({ + version: '1.2.3', + time: '2025-01-16T12:00:00.000Z', + }), ).toEqual({ version: '1.2.3', - time: '12345', + time: '2025-01-16T12:00:00.000Z', homepage: null, source: null, require: null, @@ -312,7 +316,7 @@ describe('modules/datasource/packagist/schema', () => { 'foo/bar': [ { version: 'v1.1.1', - time: '111', + time: '2025-01-16T12:00:00+00:00' as Timestamp, homepage: 'https://example.com/1', source: { url: 'git@example.com:foo/bar-1' }, require: { php: '^8.0' }, @@ -321,7 +325,7 @@ describe('modules/datasource/packagist/schema', () => { 'baz/qux': [ { version: 'v2.2.2', - time: '222', + time: '2025-01-16T12:00:00+00:00' as Timestamp, homepage: 'https://example.com/2', source: { url: 'git@example.com:baz/qux-2' }, require: null, @@ -334,7 +338,7 @@ describe('modules/datasource/packagist/schema', () => { 'foo/bar': [ { version: 'v3.3.3', - time: '333', + time: '2025-01-16T12:00:00+00:00' as Timestamp, homepage: 'https://example.com/3', source: { url: 'git@example.com:foo/bar-3' }, require: { php: '^7.0' }, @@ -343,7 +347,7 @@ describe('modules/datasource/packagist/schema', () => { 'baz/qux': [ { version: 'v4.4.4', - time: '444', + time: '2025-01-16T12:00:00+00:00' as Timestamp, homepage: 'https://example.com/4', source: { url: 'git@example.com:baz/qux-3' }, require: null, @@ -359,13 +363,13 @@ describe('modules/datasource/packagist/schema', () => { { version: '1.1.1', gitRef: 'v1.1.1', - releaseTimestamp: '111', + releaseTimestamp: '2025-01-16T12:00:00.000Z' as Timestamp, constraints: { php: ['^8.0'] }, }, { version: '3.3.3', gitRef: 'v3.3.3', - releaseTimestamp: '333', + releaseTimestamp: '2025-01-16T12:00:00.000Z' as Timestamp, constraints: { php: ['^7.0'] }, }, ], diff --git a/lib/modules/datasource/packagist/schema.ts b/lib/modules/datasource/packagist/schema.ts index 2d95b2ad95d611..29af76574d5b3c 100644 --- a/lib/modules/datasource/packagist/schema.ts +++ b/lib/modules/datasource/packagist/schema.ts @@ -2,6 +2,7 @@ import is from '@sindresorhus/is'; import { z } from 'zod'; import { logger } from '../../../logger'; import { LooseArray, LooseRecord } from '../../../util/schema-utils'; +import { MaybeTimestamp } from '../../../util/timestamp'; import type { Release, ReleaseResult } from '../types'; export const MinifiedArray = z.array(z.record(z.unknown())).transform((xs) => { @@ -44,7 +45,7 @@ export const ComposerRelease = z.object({ version: z.string(), homepage: z.string().nullable().catch(null), source: z.object({ url: z.string() }).nullable().catch(null), - time: z.string().nullable().catch(null), + time: MaybeTimestamp, require: z.object({ php: z.string() }).nullable().catch(null), }); export type ComposerRelease = z.infer; diff --git a/lib/modules/datasource/pod/index.ts b/lib/modules/datasource/pod/index.ts index 86aa0449081bf1..ca728d3955cb69 100644 --- a/lib/modules/datasource/pod/index.ts +++ b/lib/modules/datasource/pod/index.ts @@ -125,7 +125,7 @@ export class PodDatasource extends Datasource { packageName: string, ): Promise { try { - const resp = await this.githubHttp.getJson(url); + const resp = await this.githubHttp.getJsonUnchecked(url); if (resp?.body) { return resp.body; } diff --git a/lib/modules/datasource/postprocess-release.spec.ts b/lib/modules/datasource/postprocess-release.spec.ts index 3ec6de9abd8e99..9efbbbc3a8ebcf 100644 --- a/lib/modules/datasource/postprocess-release.spec.ts +++ b/lib/modules/datasource/postprocess-release.spec.ts @@ -1,4 +1,5 @@ import { mocked } from '../../../test/util'; +import type { Timestamp } from '../../util/timestamp'; import * as _datasourceCommon from './common'; import { Datasource } from './datasource'; import { postprocessRelease } from './postprocess-release'; @@ -86,7 +87,7 @@ describe('modules/datasource/postprocess-release', () => { _config: PostprocessReleaseConfig, release: Release, ): Promise { - release.releaseTimestamp = '2024-09-05'; + release.releaseTimestamp = '2024-09-05' as Timestamp; return Promise.resolve(release); } } diff --git a/lib/modules/datasource/puppet-forge/index.ts b/lib/modules/datasource/puppet-forge/index.ts index f656f4d5faad79..41b45a6a5b998c 100644 --- a/lib/modules/datasource/puppet-forge/index.ts +++ b/lib/modules/datasource/puppet-forge/index.ts @@ -1,3 +1,4 @@ +import { asTimestamp } from '../../../util/timestamp'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; import { PUPPET_FORGE } from './common'; @@ -28,7 +29,7 @@ export class PuppetForgeDatasource extends Datasource { let module: PuppetModule; try { - const response = await this.http.getJson(url); + const response = await this.http.getJsonUnchecked(url); module = response.body; } catch (err) { this.handleGenericErrors(err); @@ -37,7 +38,7 @@ export class PuppetForgeDatasource extends Datasource { const releases: Release[] = module?.releases?.map((release) => ({ version: release.version, downloadUrl: release.file_uri, - releaseTimestamp: release.created_at, + releaseTimestamp: asTimestamp(release.created_at), registryUrl, })); diff --git a/lib/modules/datasource/pypi/index.ts b/lib/modules/datasource/pypi/index.ts index 3e8812116e0555..48fda7604991f1 100644 --- a/lib/modules/datasource/pypi/index.ts +++ b/lib/modules/datasource/pypi/index.ts @@ -6,6 +6,7 @@ import { coerceArray } from '../../../util/array'; import { parse } from '../../../util/html'; import type { OutgoingHttpHeaders } from '../../../util/http/types'; import { regEx } from '../../../util/regex'; +import { asTimestamp } from '../../../util/timestamp'; import { ensureTrailingSlash, parseUrl } from '../../../util/url'; import * as pep440 from '../../versioning/pep440'; import { Datasource } from '../datasource'; @@ -110,7 +111,9 @@ export class PypiDatasource extends Datasource { const dependency: ReleaseResult = { releases: [] }; logger.trace({ lookupUrl }, 'Pypi api got lookup'); const headers = await this.getAuthHeaders(lookupUrl); - const rep = await this.http.getJson(lookupUrl, { headers }); + const rep = await this.http.getJsonUnchecked(lookupUrl, { + headers, + }); const dep = rep?.body; if (!dep) { logger.trace({ dependency: packageName }, 'pip package not found'); @@ -171,7 +174,7 @@ export class PypiDatasource extends Datasource { const isDeprecated = releases.some(({ yanked }) => yanked); const result: Release = { version, - releaseTimestamp, + releaseTimestamp: asTimestamp(releaseTimestamp), }; if (isDeprecated) { result.isDeprecated = isDeprecated; diff --git a/lib/modules/datasource/python-version/index.spec.ts b/lib/modules/datasource/python-version/index.spec.ts index 4333bfe0cec344..dea8e2d3510601 100644 --- a/lib/modules/datasource/python-version/index.spec.ts +++ b/lib/modules/datasource/python-version/index.spec.ts @@ -4,6 +4,7 @@ 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 type { Timestamp } from '../../../util/timestamp'; import { registryUrl as eolRegistryUrl } from '../endoflife-date/common'; import { datasource, defaultRegistryUrl } from './common'; import { PythonVersionDatasource } from '.'; @@ -38,7 +39,7 @@ describe('modules/datasource/python-version/index', () => { name: 'containerbase/python-prebuild', description: 'some description', version: '3.12.1', - releaseTimestamp: '2020-03-09T13:00:00Z', + releaseTimestamp: '2020-03-09T13:00:00Z' as Timestamp, }, { id: 2, @@ -46,7 +47,7 @@ describe('modules/datasource/python-version/index', () => { name: 'containerbase/python-prebuild', description: 'some description', version: '3.12.0', - releaseTimestamp: '2020-03-09T13:00:00Z', + releaseTimestamp: '2020-03-09T13:00:00Z' as Timestamp, }, { id: 3, @@ -54,7 +55,7 @@ describe('modules/datasource/python-version/index', () => { name: 'containerbase/python-prebuild', description: 'some description', version: '3.7.8', - releaseTimestamp: '2020-03-09T13:00:00Z', + releaseTimestamp: '2020-03-09T13:00:00Z' as Timestamp, }, ]); }); @@ -105,7 +106,7 @@ describe('modules/datasource/python-version/index', () => { expect(res?.releases[0]).toEqual({ isDeprecated: true, isStable: true, - releaseTimestamp: '2020-06-27T12:55:01.000Z', + releaseTimestamp: '2020-06-27T12:55:01.000Z' as Timestamp, version: '3.7.8', }); }); diff --git a/lib/modules/datasource/python-version/schema.ts b/lib/modules/datasource/python-version/schema.ts index 2f804ef8a42f4d..4aa2f7f61b697b 100644 --- a/lib/modules/datasource/python-version/schema.ts +++ b/lib/modules/datasource/python-version/schema.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { MaybeTimestamp } from '../../../util/timestamp'; import type { Release } from '../types'; export const PythonRelease = z @@ -12,7 +13,7 @@ export const PythonRelease = z /** 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(), + release_date: MaybeTimestamp, pre_release: z.boolean(), release_page: z.string().nullable(), show_on_download_page: z.boolean(), diff --git a/lib/modules/datasource/repology/index.ts b/lib/modules/datasource/repology/index.ts index 987a2c20eac197..45295b7ae785bd 100644 --- a/lib/modules/datasource/repology/index.ts +++ b/lib/modules/datasource/repology/index.ts @@ -56,7 +56,7 @@ export class RepologyDatasource extends Datasource { private async queryPackages(url: string): Promise { try { - const res = await this.http.getJson(url); + const res = await this.http.getJsonUnchecked(url); return res.body; } catch (err) { if (err.statusCode === 404) { diff --git a/lib/modules/datasource/ruby-version/index.ts b/lib/modules/datasource/ruby-version/index.ts index 7f2a2613c1d6f8..edf3f52c46d6d4 100644 --- a/lib/modules/datasource/ruby-version/index.ts +++ b/lib/modules/datasource/ruby-version/index.ts @@ -3,6 +3,7 @@ import { ExternalHostError } from '../../../types/errors/external-host-error'; import { cache } from '../../../util/cache/package/decorator'; import { parse } from '../../../util/html'; import type { HttpError } from '../../../util/http'; +import { asTimestamp } from '../../../util/timestamp'; import { isVersion, id as rubyVersioningId } from '../../versioning/ruby'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, ReleaseResult } from '../types'; @@ -51,7 +52,7 @@ export class RubyVersionDatasource extends Datasource { if (columns.length) { const version = columns[0].replace('Ruby ', ''); if (isVersion(version)) { - const releaseTimestamp = columns[1]; + const releaseTimestamp = asTimestamp(columns[1]); const changelogUrl = columns[2] .replace('more...', ''); diff --git a/lib/modules/datasource/rubygems/metadata-cache.spec.ts b/lib/modules/datasource/rubygems/metadata-cache.spec.ts index d1d611ac3c5882..2ebafc33dc489f 100644 --- a/lib/modules/datasource/rubygems/metadata-cache.spec.ts +++ b/lib/modules/datasource/rubygems/metadata-cache.spec.ts @@ -78,19 +78,19 @@ describe('modules/datasource/rubygems/metadata-cache', () => { releases: [ { version: '1.0.0', - releaseTimestamp: '2021-01-01', + releaseTimestamp: '2021-01-01T00:00:00.000Z', changelogUrl: 'https://v1.example.com/changelog', sourceUrl: 'https://v1.example.com/source', }, { version: '2.0.0', - releaseTimestamp: '2022-01-01', + releaseTimestamp: '2022-01-01T00:00:00.000Z', changelogUrl: 'https://v2.example.com/changelog', sourceUrl: 'https://v2.example.com/source', }, { version: '3.0.0', - releaseTimestamp: '2023-01-01', + releaseTimestamp: '2023-01-01T00:00:00.000Z', changelogUrl: 'https://v3.example.com/changelog', sourceUrl: 'https://v3.example.com/source', }, diff --git a/lib/modules/datasource/rubygems/schema.spec.ts b/lib/modules/datasource/rubygems/schema.spec.ts index 5ece346c68d547..ebe3fb212af071 100644 --- a/lib/modules/datasource/rubygems/schema.spec.ts +++ b/lib/modules/datasource/rubygems/schema.spec.ts @@ -97,7 +97,7 @@ describe('modules/datasource/rubygems/schema', () => { releases: [ { version: '1.0.0', - releaseTimestamp: '2021-01-01', + releaseTimestamp: '2021-01-01T00:00:00.000Z', changelogUrl: 'https://example.com', sourceUrl: 'https://example.com', constraints: { @@ -108,7 +108,7 @@ describe('modules/datasource/rubygems/schema', () => { }, { version: '2.0.0', - releaseTimestamp: '2022-01-01', + releaseTimestamp: '2022-01-01T00:00:00.000Z', changelogUrl: 'https://example.com', sourceUrl: 'https://example.com', constraints: { @@ -119,7 +119,7 @@ describe('modules/datasource/rubygems/schema', () => { }, { version: '3.0.0', - releaseTimestamp: '2023-01-01', + releaseTimestamp: '2023-01-01T00:00:00.000Z', changelogUrl: 'https://example.com', sourceUrl: 'https://example.com', constraints: { diff --git a/lib/modules/datasource/rubygems/schema.ts b/lib/modules/datasource/rubygems/schema.ts index 86c856cfebd376..63e289f93e4cb1 100644 --- a/lib/modules/datasource/rubygems/schema.ts +++ b/lib/modules/datasource/rubygems/schema.ts @@ -3,6 +3,7 @@ import { z } from 'zod'; import { filterMap } from '../../../util/filter-map'; import { newlineRegex } from '../../../util/regex'; import { LooseArray } from '../../../util/schema-utils'; +import { MaybeTimestamp } from '../../../util/timestamp'; import type { Release } from '../types'; export const MarshalledVersionInfo = LooseArray( @@ -36,7 +37,7 @@ export const GemVersions = LooseArray( z .object({ number: z.string(), - created_at: z.string(), + created_at: MaybeTimestamp, platform: z.string().optional().catch(undefined), ruby_version: z.string().optional().catch(undefined), rubygems_version: z.string().optional().catch(undefined), diff --git a/lib/modules/datasource/sbt-package/index.ts b/lib/modules/datasource/sbt-package/index.ts index c956922097486c..43697efde46245 100644 --- a/lib/modules/datasource/sbt-package/index.ts +++ b/lib/modules/datasource/sbt-package/index.ts @@ -5,6 +5,8 @@ import * as packageCache from '../../../util/cache/package'; import { cache } from '../../../util/cache/package/decorator'; import { Http } from '../../../util/http'; import { regEx } from '../../../util/regex'; +import type { Timestamp } from '../../../util/timestamp'; +import { asTimestamp } from '../../../util/timestamp'; import { ensureTrailingSlash, trimTrailingSlash } from '../../../util/url'; import * as ivyVersioning from '../../versioning/ivy'; import { compare } from '../../versioning/maven/compare'; @@ -30,7 +32,7 @@ interface ScalaDepCoordinate { interface PomInfo { homepage?: string; sourceUrl?: string; - releaseTimestamp?: string; + releaseTimestamp?: Timestamp; } export class SbtPackageDatasource extends MavenDatasource { @@ -285,7 +287,7 @@ export class SbtPackageDatasource extends MavenDatasource { const result: PomInfo = {}; - const releaseTimestamp = val.lastModified; + const releaseTimestamp = asTimestamp(val.lastModified); if (releaseTimestamp) { result.releaseTimestamp = releaseTimestamp; } diff --git a/lib/modules/datasource/terraform-module/base.ts b/lib/modules/datasource/terraform-module/base.ts index 24f6a62493e949..e2c8aba86f56e6 100644 --- a/lib/modules/datasource/terraform-module/base.ts +++ b/lib/modules/datasource/terraform-module/base.ts @@ -22,7 +22,7 @@ export abstract class TerraformDatasource extends Datasource { ): Promise { const discoveryURL = TerraformDatasource.getDiscoveryUrl(registryUrl); const serviceDiscovery = ( - await this.http.getJson(discoveryURL) + await this.http.getJsonUnchecked(discoveryURL) ).body; return serviceDiscovery; } diff --git a/lib/modules/datasource/terraform-module/index.ts b/lib/modules/datasource/terraform-module/index.ts index 4e3b25e4bfd3ec..c035536d516730 100644 --- a/lib/modules/datasource/terraform-module/index.ts +++ b/lib/modules/datasource/terraform-module/index.ts @@ -2,6 +2,7 @@ import { logger } from '../../../logger'; import { cache } from '../../../util/cache/package/decorator'; import { regEx } from '../../../util/regex'; import { coerceString } from '../../../util/string'; +import { asTimestamp } from '../../../util/timestamp'; import { isHttpUrl } from '../../../util/url'; import * as hashicorpVersioning from '../../versioning/hashicorp'; import type { GetReleasesConfig, ReleaseResult } from '../types'; @@ -103,7 +104,7 @@ export class TerraformModuleDatasource extends TerraformDatasource { serviceDiscovery, repository, ); - res = (await this.http.getJson(pkgUrl)).body; + res = (await this.http.getJsonUnchecked(pkgUrl)).body; const returnedName = res.namespace + '/' + res.name + '/' + res.provider; if (returnedName !== repository) { logger.warn({ pkgUrl }, 'Terraform registry result mismatch'); @@ -128,7 +129,7 @@ export class TerraformModuleDatasource extends TerraformDatasource { (release) => res.version === release.version, ); if (latestVersion) { - latestVersion.releaseTimestamp = res.published_at; + latestVersion.releaseTimestamp = asTimestamp(res.published_at); } return dep; } @@ -152,7 +153,8 @@ export class TerraformModuleDatasource extends TerraformDatasource { serviceDiscovery, `${repository}/versions`, ); - res = (await this.http.getJson(pkgUrl)).body; + res = (await this.http.getJsonUnchecked(pkgUrl)) + .body; if (res.modules.length < 1) { logger.warn({ pkgUrl }, 'Terraform registry result mismatch'); return null; diff --git a/lib/modules/datasource/terraform-provider/index.ts b/lib/modules/datasource/terraform-provider/index.ts index b960f8cf424803..0f3e72dcacab75 100644 --- a/lib/modules/datasource/terraform-provider/index.ts +++ b/lib/modules/datasource/terraform-provider/index.ts @@ -5,6 +5,7 @@ import { ExternalHostError } from '../../../types/errors/external-host-error'; import { cache } from '../../../util/cache/package/decorator'; import * as p from '../../../util/promises'; import { regEx } from '../../../util/regex'; +import { asTimestamp } from '../../../util/timestamp'; import { joinUrlParts } from '../../../util/url'; import * as hashicorpVersioning from '../../versioning/hashicorp'; import { TerraformDatasource } from '../terraform-module/base'; @@ -113,7 +114,9 @@ export class TerraformProviderDatasource extends TerraformDatasource { serviceDiscovery, repository, ); - const res = (await this.http.getJson(backendURL)).body; + const res = ( + await this.http.getJsonUnchecked(backendURL) + ).body; const dep: ReleaseResult = { releases: res.versions.map((version) => ({ version, @@ -128,7 +131,7 @@ export class TerraformProviderDatasource extends TerraformDatasource { ); // istanbul ignore else if (latestVersion) { - latestVersion.releaseTimestamp = res.published_at; + latestVersion.releaseTimestamp = asTimestamp(res.published_at); } dep.homepage = `${registryUrl}/providers/${repository}`; return dep; @@ -149,8 +152,9 @@ export class TerraformProviderDatasource extends TerraformDatasource { serviceDiscovery, `${repository}/versions`, ); - const res = (await this.http.getJson(backendURL)) - .body; + const res = ( + await this.http.getJsonUnchecked(backendURL) + ).body; const dep: ReleaseResult = { releases: res.versions.map(({ version }) => ({ version, @@ -171,7 +175,9 @@ export class TerraformProviderDatasource extends TerraformDatasource { `index.json`, ); const res = ( - await this.http.getJson(backendURL) + await this.http.getJsonUnchecked( + backendURL, + ) ).body; const dep: ReleaseResult = { @@ -240,7 +246,7 @@ export class TerraformProviderDatasource extends TerraformDatasource { repository, ); const versionsResponse = ( - await this.http.getJson( + await this.http.getJsonUnchecked( `${backendURL}/versions`, ) ).body; @@ -263,7 +269,9 @@ export class TerraformProviderDatasource extends TerraformDatasource { const buildURL = `${backendURL}/${version}/download/${platform.os}/${platform.arch}`; try { const res = ( - await this.http.getJson(buildURL) + await this.http.getJsonUnchecked( + buildURL, + ) ).body; const newBuild: TerraformBuild = { name: repository, @@ -325,7 +333,7 @@ export class TerraformProviderDatasource extends TerraformDatasource { version: string, ): Promise { return ( - await this.http.getJson( + await this.http.getJsonUnchecked( `${TerraformProviderDatasource.defaultRegistryUrls[1]}/${backendLookUpName}/${version}/index.json`, ) ).body; diff --git a/lib/modules/datasource/types.ts b/lib/modules/datasource/types.ts index aea32290d9fbce..9e8e8300819467 100644 --- a/lib/modules/datasource/types.ts +++ b/lib/modules/datasource/types.ts @@ -3,6 +3,7 @@ import type { CustomDatasourceConfig, } from '../../config/types'; import type { ModuleApi } from '../../types'; +import type { Timestamp } from '../../util/timestamp'; export interface GetDigestInputConfig { datasource: string; @@ -59,7 +60,7 @@ export interface Release { gitRef?: string; isDeprecated?: boolean; isStable?: boolean; - releaseTimestamp?: string | null; + releaseTimestamp?: Timestamp | null; version: string; /** The original value to which `extractVersion` was applied */ versionOrig?: string; diff --git a/lib/modules/datasource/unity3d/index.ts b/lib/modules/datasource/unity3d/index.ts index a54bbb03e61085..e134d9aba3a667 100644 --- a/lib/modules/datasource/unity3d/index.ts +++ b/lib/modules/datasource/unity3d/index.ts @@ -2,6 +2,7 @@ import type { XmlElement } from 'xmldoc'; import { XmlDocument } from 'xmldoc'; import { logger } from '../../../logger'; import { cache } from '../../../util/cache/package/decorator'; +import { asTimestamp } from '../../../util/timestamp'; import * as Unity3dVersioning from '../../versioning/unity3d'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; @@ -64,7 +65,7 @@ export class Unity3dDatasource extends Datasource { const versionWithoutHash = itemNode.childNamed('title')?.val; const release: Release = { version: withHash ? versionWithHash : versionWithoutHash!, - releaseTimestamp: itemNode.childNamed('pubDate')?.val, + releaseTimestamp: asTimestamp(itemNode.childNamed('pubDate')?.val), changelogUrl: itemNode.childNamed('link')?.val, isStable: registryUrl !== Unity3dDatasource.streams.beta, registryUrl, diff --git a/lib/modules/datasource/util.ts b/lib/modules/datasource/util.ts index b418bb9bb7c560..e53566d786f63f 100644 --- a/lib/modules/datasource/util.ts +++ b/lib/modules/datasource/util.ts @@ -1,6 +1,7 @@ import is from '@sindresorhus/is'; import { GoogleAuth } from 'google-auth-library'; import { logger } from '../../logger'; +import type { HostRule } from '../../types'; import type { HttpResponse } from '../../util/http/types'; import { addSecretForSanitizing } from '../../util/sanitize'; @@ -12,7 +13,7 @@ export function isArtifactoryServer( return is.string(res?.headers[JFROG_ARTIFACTORY_RES_HEADER]); } -export async function getGoogleAuthTokenRaw(): Promise { +export async function getGoogleAuthHostRule(): Promise { try { const googleAuth: GoogleAuth = new GoogleAuth({ scopes: 'https://www.googleapis.com/auth/cloud-platform', @@ -21,7 +22,10 @@ export async function getGoogleAuthTokenRaw(): Promise { if (accessToken) { // sanitize token addSecretForSanitizing(accessToken); - return accessToken; + return { + username: 'oauth2accesstoken', + password: accessToken, + }; } else { logger.warn( 'Could not retrieve access token using google-auth-library getAccessToken', @@ -38,9 +42,13 @@ export async function getGoogleAuthTokenRaw(): Promise { } export async function getGoogleAuthToken(): Promise { - const accessToken = await getGoogleAuthTokenRaw(); - if (accessToken) { - return Buffer.from(`oauth2accesstoken:${accessToken}`).toString('base64'); + const rule = await getGoogleAuthHostRule(); + if (rule) { + const token = Buffer.from(`${rule.username}:${rule.password}`).toString( + 'base64', + ); + addSecretForSanitizing(token); + return token; } return null; } diff --git a/lib/modules/manager/ansible/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/ansible/__snapshots__/extract.spec.ts.snap index 27860f5b64f3c0..826e16a14f124b 100644 --- a/lib/modules/manager/ansible/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/ansible/__snapshots__/extract.spec.ts.snap @@ -8,8 +8,8 @@ exports[`modules/manager/ansible/extract extractPackageFile() extracts multiple "currentValue": undefined, "datasource": "docker", "depName": "busybox", + "packageName": "busybox", "replaceString": "busybox", - "versioning": "docker", }, { "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", @@ -17,8 +17,8 @@ exports[`modules/manager/ansible/extract extractPackageFile() extracts multiple "currentValue": undefined, "datasource": "docker", "depName": "redis", + "packageName": "redis", "replaceString": "redis", - "versioning": "docker", }, { "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", @@ -26,8 +26,8 @@ exports[`modules/manager/ansible/extract extractPackageFile() extracts multiple "currentValue": undefined, "datasource": "docker", "depName": "someuser/appimage", + "packageName": "someuser/appimage", "replaceString": "someuser/appimage", - "versioning": "docker", }, { "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", @@ -35,8 +35,9 @@ exports[`modules/manager/ansible/extract extractPackageFile() extracts multiple "currentValue": "14.04", "datasource": "docker", "depName": "ubuntu", + "packageName": "ubuntu", "replaceString": "ubuntu:14.04", - "versioning": "docker", + "versioning": "ubuntu", }, { "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", @@ -44,8 +45,8 @@ exports[`modules/manager/ansible/extract extractPackageFile() extracts multiple "currentValue": undefined, "datasource": "docker", "depName": "someuser/anotherappimage", + "packageName": "someuser/anotherappimage", "replaceString": "someuser/anotherappimage", - "versioning": "docker", }, { "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", @@ -53,8 +54,8 @@ exports[`modules/manager/ansible/extract extractPackageFile() extracts multiple "currentValue": undefined, "datasource": "docker", "depName": "busybox", + "packageName": "busybox", "replaceString": "busybox", - "versioning": "docker", }, { "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", @@ -62,8 +63,8 @@ exports[`modules/manager/ansible/extract extractPackageFile() extracts multiple "currentValue": "latest", "datasource": "docker", "depName": "postgres", + "packageName": "postgres", "replaceString": "postgres:latest", - "versioning": "docker", }, { "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", @@ -71,8 +72,9 @@ exports[`modules/manager/ansible/extract extractPackageFile() extracts multiple "currentValue": "14.04", "datasource": "docker", "depName": "ubuntu", + "packageName": "ubuntu", "replaceString": "ubuntu:14.04", - "versioning": "docker", + "versioning": "ubuntu", }, { "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", @@ -80,8 +82,9 @@ exports[`modules/manager/ansible/extract extractPackageFile() extracts multiple "currentValue": "14.04", "datasource": "docker", "depName": "ubuntu", + "packageName": "ubuntu", "replaceString": "ubuntu:14.04", - "versioning": "docker", + "versioning": "ubuntu", }, ] `; @@ -94,8 +97,8 @@ exports[`modules/manager/ansible/extract extractPackageFile() extracts multiple "currentValue": "11.5.1", "datasource": "docker", "depName": "sameersbn/gitlab", + "packageName": "sameersbn/gitlab", "replaceString": "sameersbn/gitlab:11.5.1", - "versioning": "docker", }, { "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", @@ -103,8 +106,8 @@ exports[`modules/manager/ansible/extract extractPackageFile() extracts multiple "currentValue": "10", "datasource": "docker", "depName": "sameersbn/postgresql", + "packageName": "sameersbn/postgresql", "replaceString": "sameersbn/postgresql:10", - "versioning": "docker", }, { "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", @@ -112,8 +115,8 @@ exports[`modules/manager/ansible/extract extractPackageFile() extracts multiple "currentValue": "4.0.9-1", "datasource": "docker", "depName": "sameersbn/redis", + "packageName": "sameersbn/redis", "replaceString": "sameersbn/redis:4.0.9-1", - "versioning": "docker", }, { "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", @@ -121,8 +124,8 @@ exports[`modules/manager/ansible/extract extractPackageFile() extracts multiple "currentValue": "2.6.2", "datasource": "docker", "depName": "registry", + "packageName": "registry", "replaceString": "registry:2.6.2", - "versioning": "docker", }, ] `; diff --git a/lib/modules/manager/ansible/extract.spec.ts b/lib/modules/manager/ansible/extract.spec.ts index b807ce7eb5a2fe..b0e19ed901bbca 100644 --- a/lib/modules/manager/ansible/extract.spec.ts +++ b/lib/modules/manager/ansible/extract.spec.ts @@ -41,9 +41,9 @@ describe('modules/manager/ansible/extract', () => { currentDigest: undefined, currentValue: '0.0.1', datasource: 'docker', - depName: 'my-quay-mirror.registry.com/redis', + depName: 'quay.io/redis', + packageName: 'my-quay-mirror.registry.com/redis', replaceString: 'quay.io/redis:0.0.1', - versioning: 'docker', }, ], }); @@ -72,8 +72,8 @@ describe('modules/manager/ansible/extract', () => { currentValue: '0.0.1', datasource: 'docker', depName: 'quay.io/redis', + packageName: 'quay.io/redis', replaceString: 'quay.io/redis:0.0.1', - versioning: 'docker', }, ], }); @@ -102,9 +102,9 @@ describe('modules/manager/ansible/extract', () => { currentDigest: undefined, currentValue: '0.0.1', datasource: 'docker', - depName: 'my-quay-mirror.registry.com/redis', + depName: 'quay.io/redis', + packageName: 'my-quay-mirror.registry.com/redis', replaceString: 'quay.io/redis:0.0.1', - versioning: 'docker', }, ], }); diff --git a/lib/modules/manager/ansible/extract.ts b/lib/modules/manager/ansible/extract.ts index 847b1104d69b4b..cc5132d102a67b 100644 --- a/lib/modules/manager/ansible/extract.ts +++ b/lib/modules/manager/ansible/extract.ts @@ -1,6 +1,5 @@ import { logger } from '../../../logger'; import { newlineRegex, regEx } from '../../../util/regex'; -import * as dockerVersioning from '../../versioning/docker'; import { getDep } from '../dockerfile/extract'; import type { ExtractConfig, @@ -29,7 +28,6 @@ export function extractPackageFile( }, 'Docker image inside ansible', ); - dep.versioning = dockerVersioning.id; deps.push(dep); } } diff --git a/lib/modules/manager/api.ts b/lib/modules/manager/api.ts index e8522f73326a9e..259f893928a03c 100644 --- a/lib/modules/manager/api.ts +++ b/lib/modules/manager/api.ts @@ -28,6 +28,7 @@ import * as copier from './copier'; import * as cpanfile from './cpanfile'; import * as crossplane from './crossplane'; import * as depsEdn from './deps-edn'; +import * as devbox from './devbox'; import * as devContainer from './devcontainer'; import * as dockerCompose from './docker-compose'; import * as dockerfile from './dockerfile'; @@ -44,6 +45,7 @@ import * as gleam from './gleam'; import * as gomod from './gomod'; import * as gradle from './gradle'; import * as gradleWrapper from './gradle-wrapper'; +import * as haskellCabal from './haskell-cabal'; import * as helmRequirements from './helm-requirements'; import * as helmValues from './helm-values'; import * as helmfile from './helmfile'; @@ -134,6 +136,7 @@ api.set('copier', copier); api.set('cpanfile', cpanfile); api.set('crossplane', crossplane); api.set('deps-edn', depsEdn); +api.set('devbox', devbox); api.set('devcontainer', devContainer); api.set('docker-compose', dockerCompose); api.set('dockerfile', dockerfile); @@ -150,6 +153,7 @@ api.set('gleam', gleam); api.set('gomod', gomod); api.set('gradle', gradle); api.set('gradle-wrapper', gradleWrapper); +api.set('haskell-cabal', haskellCabal); api.set('helm-requirements', helmRequirements); api.set('helm-values', helmValues); api.set('helmfile', helmfile); diff --git a/lib/modules/manager/argocd/extract.spec.ts b/lib/modules/manager/argocd/extract.spec.ts index 3f8917dac6c04a..3438de673dd176 100644 --- a/lib/modules/manager/argocd/extract.spec.ts +++ b/lib/modules/manager/argocd/extract.spec.ts @@ -114,6 +114,7 @@ spec: currentValue: 'v2.3.4', datasource: 'docker', depName: 'somecontainer.registry.io/someContainer', + packageName: 'somecontainer.registry.io/someContainer', replaceString: 'somecontainer.registry.io/someContainer:v2.3.4', }, { @@ -123,6 +124,7 @@ spec: 'sha256:8be5de38826b494a8ad1565b8d1eb49183d736d0277a89191bd1100d78479a42', datasource: 'docker', depName: 'othercontainer.registry.io/other/container', + packageName: 'othercontainer.registry.io/other/container', replaceString: 'othercontainer.registry.io/other/container@sha256:8be5de38826b494a8ad1565b8d1eb49183d736d0277a89191bd1100d78479a42', }, diff --git a/lib/modules/manager/argocd/extract.ts b/lib/modules/manager/argocd/extract.ts index 520415366891b0..4ff2de9418bbf6 100644 --- a/lib/modules/manager/argocd/extract.ts +++ b/lib/modules/manager/argocd/extract.ts @@ -2,8 +2,8 @@ import is from '@sindresorhus/is'; import { logger } from '../../../logger'; import { coerceArray } from '../../../util/array'; import { regEx } from '../../../util/regex'; +import { withDebugMessage } from '../../../util/schema-utils'; import { trimTrailingSlash } from '../../../util/url'; -import { parseYaml } from '../../../util/yaml'; import { DockerDatasource } from '../../datasource/docker'; import { GitTagsDatasource } from '../../datasource/git-tags'; import { HelmDatasource } from '../../datasource/helm'; @@ -15,7 +15,8 @@ import type { PackageFileContent, } from '../types'; import { - ApplicationDefinition, + type ApplicationDefinition, + ApplicationDefinitionSchema, type ApplicationSource, type ApplicationSpec, } from './schema'; @@ -36,11 +37,9 @@ export function extractPackageFile( return null; } - const definitions = parseYaml(content, { - customSchema: ApplicationDefinition, - failureBehaviour: 'filter', - removeTemplates: true, - }); + const definitions = ApplicationDefinitionSchema.catch( + withDebugMessage([], `error parsing ${packageFile}`), + ).parse(content); const deps = definitions.flatMap(processAppSpec); diff --git a/lib/modules/manager/argocd/schema.ts b/lib/modules/manager/argocd/schema.ts index 4fb8e636829282..395f7f9a9d6ec2 100644 --- a/lib/modules/manager/argocd/schema.ts +++ b/lib/modules/manager/argocd/schema.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { LooseArray } from '../../../util/schema-utils'; +import { LooseArray, multidocYaml } from '../../../util/schema-utils'; export const KubernetesResource = z.object({ apiVersion: z.string(), @@ -38,3 +38,7 @@ export const ApplicationSet = KubernetesResource.extend({ export const ApplicationDefinition = Application.or(ApplicationSet); export type ApplicationDefinition = z.infer; + +export const ApplicationDefinitionSchema = multidocYaml({ + removeTemplates: true, +}).pipe(LooseArray(ApplicationDefinition)); diff --git a/lib/modules/manager/azure-pipelines/readme.md b/lib/modules/manager/azure-pipelines/readme.md index 47d9fe7058aacf..952462a64103ee 100644 --- a/lib/modules/manager/azure-pipelines/readme.md +++ b/lib/modules/manager/azure-pipelines/readme.md @@ -44,7 +44,7 @@ resources: - container: linux image: ubuntu:24.04 - container: python - image: python:3.13@sha256:cea505b81701dd9e46b8dde96eaa8054c4bd2035dbb660edeb7af947ed38a0ad + image: python:3.13@sha256:137ae4b9f85671bd912a82a19b6966e2655f73e13579b5d6ad4edbddaaf62a9c stages: - stage: StageOne diff --git a/lib/modules/manager/batect/extract.spec.ts b/lib/modules/manager/batect/extract.spec.ts index eaf2075522e355..d942f2397fdabf 100644 --- a/lib/modules/manager/batect/extract.spec.ts +++ b/lib/modules/manager/batect/extract.spec.ts @@ -1,7 +1,6 @@ import { GlobalConfig } from '../../../config/global'; import type { RepoGlobalConfig } from '../../../config/types'; import { GitTagsDatasource } from '../../datasource/git-tags'; -import { id as dockerVersioning } from '../../versioning/docker'; import { id as semverVersioning } from '../../versioning/semver'; import { getDep } from '../dockerfile/extract'; import type { ExtractConfig, PackageDependency } from '../types'; @@ -10,10 +9,7 @@ import { extractAllPackageFiles } from '.'; const fixturesDir = 'lib/modules/manager/batect/__fixtures__'; function createDockerDependency(tag: string): PackageDependency { - return { - ...getDep(tag), - versioning: dockerVersioning, - }; + return getDep(tag); } function createGitDependency(repo: string, version: string): PackageDependency { diff --git a/lib/modules/manager/batect/extract.ts b/lib/modules/manager/batect/extract.ts index d7ca1e4e6299f9..4644131b7e8219 100644 --- a/lib/modules/manager/batect/extract.ts +++ b/lib/modules/manager/batect/extract.ts @@ -1,121 +1,9 @@ -import is from '@sindresorhus/is'; import upath from 'upath'; import { logger } from '../../../logger'; import { readLocalFile } from '../../../util/fs'; -import { parseSingleYaml } from '../../../util/yaml'; -import { GitTagsDatasource } from '../../datasource/git-tags'; -import { id as dockerVersioning } from '../../versioning/docker'; -import { id as semverVersioning } from '../../versioning/semver'; -import { getDep } from '../dockerfile/extract'; -import type { ExtractConfig, PackageDependency, PackageFile } from '../types'; -import type { - BatectConfig, - BatectFileInclude, - BatectGitInclude, - BatectInclude, - ExtractionResult, -} from './types'; - -function loadConfig(content: string): BatectConfig { - const config = parseSingleYaml(content); - - if (typeof config !== 'object') { - throw new Error( - `Configuration file does not contain a YAML object (it is ${typeof config}).`, - ); - } - - return config as BatectConfig; -} - -function extractImages(config: BatectConfig): string[] { - if (config.containers === undefined) { - return []; - } - - return Object.values(config.containers) - .map((container) => container.image) - .filter(is.string); -} - -function createImageDependency(tag: string): PackageDependency { - return { - ...getDep(tag), - versioning: dockerVersioning, - }; -} - -function extractImageDependencies(config: BatectConfig): PackageDependency[] { - const images = extractImages(config); - const deps = images.map((image) => createImageDependency(image)); - - logger.trace({ deps }, 'Loaded images from Batect configuration file'); - - return deps; -} - -function includeIsGitInclude( - include: BatectInclude, -): include is BatectGitInclude { - return typeof include === 'object' && include.type === 'git'; -} - -function extractGitBundles(config: BatectConfig): BatectGitInclude[] { - if (config.include === undefined) { - return []; - } - - return config.include.filter(includeIsGitInclude); -} - -function createBundleDependency(bundle: BatectGitInclude): PackageDependency { - return { - depName: bundle.repo, - currentValue: bundle.ref, - versioning: semverVersioning, - datasource: GitTagsDatasource.id, - commitMessageTopic: 'bundle {{depName}}', - }; -} - -function extractBundleDependencies(config: BatectConfig): PackageDependency[] { - const bundles = extractGitBundles(config); - const deps = bundles.map((bundle) => createBundleDependency(bundle)); - - logger.trace({ deps }, 'Loaded bundles from Batect configuration file'); - - return deps; -} - -function includeIsStringFileInclude(include: BatectInclude): include is string { - return typeof include === 'string'; -} - -function includeIsObjectFileInclude( - include: BatectInclude, -): include is BatectFileInclude { - return typeof include === 'object' && include.type === 'file'; -} - -function extractReferencedConfigFiles( - config: BatectConfig, - fileName: string, -): string[] { - if (config.include === undefined) { - return []; - } - - const dirName = upath.dirname(fileName); - - const paths = [ - ...config.include.filter(includeIsStringFileInclude), - ...config.include - .filter(includeIsObjectFileInclude) - .map((include) => include.path), - ].filter((p) => p !== undefined && p !== null); - - return paths.map((p) => upath.join(dirName, p)); -} +import type { ExtractConfig, PackageFile } from '../types'; +import { BatectConfigSchema } from './schema'; +import type { ExtractionResult } from './types'; export function extractPackageFile( content: string, @@ -124,15 +12,13 @@ export function extractPackageFile( logger.trace(`batect.extractPackageFile(${packageFile})`); try { - const config = loadConfig(content); - const deps = [ - ...extractImageDependencies(config), - ...extractBundleDependencies(config), - ]; + const { imageDependencies, bundleDependencies, fileIncludes } = + BatectConfigSchema.parse(content); + const deps = [...imageDependencies, ...bundleDependencies]; - const referencedConfigFiles = extractReferencedConfigFiles( - config, - packageFile, + const dirName = upath.dirname(packageFile); + const referencedConfigFiles = fileIncludes.map((file) => + upath.join(dirName, file), ); return { deps, referencedConfigFiles }; diff --git a/lib/modules/manager/batect/index.ts b/lib/modules/manager/batect/index.ts index de751ad55097b8..a8811fc6644da3 100644 --- a/lib/modules/manager/batect/index.ts +++ b/lib/modules/manager/batect/index.ts @@ -1,4 +1,5 @@ import type { Category } from '../../../constants'; +import { DockerDatasource } from '../../datasource/docker'; import { GitTagsDatasource } from '../../datasource/git-tags'; import { extractAllPackageFiles, extractPackageFile } from './extract'; @@ -11,4 +12,4 @@ export const defaultConfig = { fileMatch: ['(^|/)batect(-bundle)?\\.ya?ml$'], }; -export const supportedDatasources = [GitTagsDatasource.id]; +export const supportedDatasources = [DockerDatasource.id, GitTagsDatasource.id]; diff --git a/lib/modules/manager/batect/schema.ts b/lib/modules/manager/batect/schema.ts new file mode 100644 index 00000000000000..c400448c075485 --- /dev/null +++ b/lib/modules/manager/batect/schema.ts @@ -0,0 +1,59 @@ +import { z } from 'zod'; +import { LooseArray, LooseRecord, Yaml } from '../../../util/schema-utils'; +import { GitTagsDatasource } from '../../datasource/git-tags'; +import { id as semverVersioning } from '../../versioning/semver'; +import { getDep } from '../dockerfile/extract'; +import type { PackageDependency } from '../types'; + +export const BatectConfigSchema = Yaml.pipe( + z.object({ + containers: LooseRecord( + z.string(), + z.object({ image: z.string() }).transform(({ image }) => image), + ) + .transform((x) => Object.values(x)) + .catch([]), + include: LooseArray( + z.union([ + z.object({ + type: z.literal('git'), + repo: z.string(), + ref: z.string(), + }), + z.object({ + type: z.literal('file'), + path: z.string(), + }), + z.string().transform((path) => ({ type: 'file' as const, path })), + ]), + ).catch([]), + }), +).transform(({ containers, include }) => { + // TODO: @zharinov How to pass `registryAliases` to `getDep`? + const imageDependencies = containers.map((image) => getDep(image)); + + const bundleDependencies: PackageDependency[] = []; + const fileIncludes: string[] = []; + + for (const item of include) { + if (item.type === 'git') { + bundleDependencies.push({ + depName: item.repo, + currentValue: item.ref, + versioning: semverVersioning, + datasource: GitTagsDatasource.id, + commitMessageTopic: 'bundle {{depName}}', + }); + } else { + fileIncludes.push(item.path); + } + } + + return { + imageDependencies, + bundleDependencies, + fileIncludes, + }; +}); + +export type BatectConfig = z.infer; diff --git a/lib/modules/manager/batect/types.ts b/lib/modules/manager/batect/types.ts index c5a1765e7060cd..ff439c63266c68 100644 --- a/lib/modules/manager/batect/types.ts +++ b/lib/modules/manager/batect/types.ts @@ -1,27 +1,5 @@ import type { PackageDependency } from '../types'; -export interface BatectConfig { - containers?: Record; - include?: BatectInclude[]; -} - -export interface BatectContainer { - image?: string; -} - -export type BatectInclude = string | BatectFileInclude | BatectGitInclude; - -export interface BatectFileInclude { - type: 'file'; - path: string; -} - -export interface BatectGitInclude { - type: 'git'; - repo: string; - ref: string; -} - export interface ExtractionResult { deps: PackageDependency[]; referencedConfigFiles: string[]; diff --git a/lib/modules/manager/bazel-module/context.spec.ts b/lib/modules/manager/bazel-module/context.spec.ts index f2dc8d7b993073..a134bb69fe8097 100644 --- a/lib/modules/manager/bazel-module/context.spec.ts +++ b/lib/modules/manager/bazel-module/context.spec.ts @@ -24,6 +24,24 @@ describe('modules/manager/bazel-module/context', () => { ]); }); + it('construct simple bazel_dep with no version', () => { + const ctx = new Ctx() + .startRule('bazel_dep') + .startAttribute('name') + .addString('rules_foo') + .endRule(); + + expect(ctx.results).toEqual([ + fragments.record( + { + rule: fragments.string('bazel_dep'), + name: fragments.string('rules_foo'), + }, + true, + ), + ]); + }); + it('construct a rule with array arg', () => { const ctx = new Ctx() .startRule('foo_library') diff --git a/lib/modules/manager/bazel-module/extract.spec.ts b/lib/modules/manager/bazel-module/extract.spec.ts index 53e5ca47845b30..58e7652cd55111 100644 --- a/lib/modules/manager/bazel-module/extract.spec.ts +++ b/lib/modules/manager/bazel-module/extract.spec.ts @@ -66,20 +66,52 @@ describe('modules/manager/bazel-module/extract', () => { if (!result) { throw new Error('Expected a result.'); } - expect(result.deps).toHaveLength(3); - expect(result.deps).toEqual( - expect.arrayContaining([ + + expect(result).toEqual({ + deps: [ + { + datasource: BazelDatasource.id, + depType: 'bazel_dep', + depName: 'rules_foo', + currentValue: '1.2.3', + skipReason: 'git-dependency', + }, + { + depType: 'git_override', + depName: 'rules_foo', + currentDigest: '850cb49c8649e463b80ef7984e7c744279746170', + datasource: GithubTagsDatasource.id, + packageName: 'example/rules_foo', + }, { datasource: BazelDatasource.id, depType: 'bazel_dep', depName: 'rules_bar', currentValue: '1.0.0', }, + ], + }); + }); + + it('returns bazel_dep with no version and git_override', async () => { + const input = codeBlock` + bazel_dep(name = "rules_foo") + git_override( + module_name = "rules_foo", + commit = "850cb49c8649e463b80ef7984e7c744279746170", + remote = "https://github.com/example/rules_foo.git", + ) + `; + const result = await extractPackageFile(input, 'MODULE.bazel'); + if (!result) { + throw new Error('Expected a result.'); + } + expect(result).toEqual({ + deps: [ { datasource: BazelDatasource.id, depType: 'bazel_dep', depName: 'rules_foo', - currentValue: '1.2.3', skipReason: 'git-dependency', }, { @@ -89,8 +121,8 @@ describe('modules/manager/bazel-module/extract', () => { currentDigest: '850cb49c8649e463b80ef7984e7c744279746170', packageName: 'example/rules_foo', }, - ]), - ); + ], + }); }); it('returns dependencies and custom registry URLs when specified in a bazelrc', async () => { @@ -130,9 +162,9 @@ describe('modules/manager/bazel-module/extract', () => { if (!result) { throw new Error('Expected a result.'); } - expect(result.deps).toHaveLength(2); - expect(result.deps).toEqual( - expect.arrayContaining([ + + expect(result).toEqual({ + deps: [ { datasource: BazelDatasource.id, depType: 'bazel_dep', @@ -145,8 +177,40 @@ describe('modules/manager/bazel-module/extract', () => { depName: 'rules_foo', skipReason: 'unsupported-datasource', }, - ]), - ); + ], + }); + }); + + it('returns bazel_dep with no version and archive_override dependencies', async () => { + const input = codeBlock` + bazel_dep(name = "rules_foo") + archive_override( + module_name = "rules_foo", + urls = [ + "https://example.com/archive.tar.gz", + ], + ) + `; + const result = await extractPackageFile(input, 'MODULE.bazel'); + if (!result) { + throw new Error('Expected a result.'); + } + + expect(result).toEqual({ + deps: [ + { + datasource: BazelDatasource.id, + depType: 'bazel_dep', + depName: 'rules_foo', + skipReason: 'file-dependency', + }, + { + depType: 'archive_override', + depName: 'rules_foo', + skipReason: 'unsupported-datasource', + }, + ], + }); }); it('returns bazel_dep and local_path_override dependencies', async () => { @@ -161,9 +225,9 @@ describe('modules/manager/bazel-module/extract', () => { if (!result) { throw new Error('Expected a result.'); } - expect(result.deps).toHaveLength(2); - expect(result.deps).toEqual( - expect.arrayContaining([ + + expect(result).toEqual({ + deps: [ { datasource: BazelDatasource.id, depType: 'bazel_dep', @@ -176,8 +240,38 @@ describe('modules/manager/bazel-module/extract', () => { depName: 'rules_foo', skipReason: 'unsupported-datasource', }, - ]), - ); + ], + }); + }); + + it('returns bazel_dep with no version and local_path_override dependencies', async () => { + const input = codeBlock` + bazel_dep(name = "rules_foo") + local_path_override( + module_name = "rules_foo", + urls = "/path/to/repo", + ) + `; + const result = await extractPackageFile(input, 'MODULE.bazel'); + if (!result) { + throw new Error('Expected a result.'); + } + + expect(result).toEqual({ + deps: [ + { + datasource: BazelDatasource.id, + depType: 'bazel_dep', + depName: 'rules_foo', + skipReason: 'local-dependency', + }, + { + depType: 'local_path_override', + depName: 'rules_foo', + skipReason: 'unsupported-datasource', + }, + ], + }); }); it('returns bazel_dep and single_version_override dependencies if a version is specified', async () => { @@ -185,7 +279,7 @@ describe('modules/manager/bazel-module/extract', () => { bazel_dep(name = "rules_foo", version = "1.2.3") single_version_override( module_name = "rules_foo", - version = "1.2.3", + version = "1.2.5", registry = "https://example.com/custom_registry", ) `; @@ -193,9 +287,9 @@ describe('modules/manager/bazel-module/extract', () => { if (!result) { throw new Error('Expected a result.'); } - expect(result.deps).toHaveLength(2); - expect(result.deps).toEqual( - expect.arrayContaining([ + + expect(result).toEqual({ + deps: [ { datasource: BazelDatasource.id, depType: 'bazel_dep', @@ -207,12 +301,46 @@ describe('modules/manager/bazel-module/extract', () => { { depType: 'single_version_override', depName: 'rules_foo', - currentValue: '1.2.3', skipReason: 'ignored', + currentValue: '1.2.5', registryUrls: ['https://example.com/custom_registry'], }, - ]), - ); + ], + }); + }); + + it('returns bazel_dep with no version and single_version_override dependencies if a version is specified', async () => { + const input = codeBlock` + bazel_dep(name = "rules_foo") + single_version_override( + module_name = "rules_foo", + version = "1.2.3", + registry = "https://example.com/custom_registry", + ) + `; + const result = await extractPackageFile(input, 'MODULE.bazel'); + if (!result) { + throw new Error('Expected a result.'); + } + + expect(result).toEqual({ + deps: [ + { + datasource: BazelDatasource.id, + depType: 'bazel_dep', + depName: 'rules_foo', + skipReason: 'is-pinned', + registryUrls: ['https://example.com/custom_registry'], + }, + { + depType: 'single_version_override', + depName: 'rules_foo', + skipReason: 'ignored', + currentValue: '1.2.3', + registryUrls: ['https://example.com/custom_registry'], + }, + ], + }); }); it('returns bazel_dep dependency if single_version_override does not have a version', async () => { @@ -227,15 +355,40 @@ describe('modules/manager/bazel-module/extract', () => { if (!result) { throw new Error('Expected a result.'); } - expect(result.deps).toEqual([ - { - datasource: BazelDatasource.id, - depType: 'bazel_dep', - depName: 'rules_foo', - currentValue: '1.2.3', - registryUrls: ['https://example.com/custom_registry'], - }, - ]); + + expect(result).toEqual({ + deps: [ + { + datasource: BazelDatasource.id, + depType: 'bazel_dep', + depName: 'rules_foo', + currentValue: '1.2.3', + registryUrls: ['https://example.com/custom_registry'], + }, + ], + }); + }); + + it('returns bazel_dep with no version dependency if single_version_override does not have a version', async () => { + const input = codeBlock` + bazel_dep(name = "rules_foo") + single_version_override( + module_name = "rules_foo", + registry = "https://example.com/custom_registry", + ) + `; + const result = await extractPackageFile(input, 'MODULE.bazel'); + expect(result).toEqual({ + deps: [ + { + datasource: BazelDatasource.id, + depType: 'bazel_dep', + depName: 'rules_foo', + skipReason: 'unspecified-version', + registryUrls: ['https://example.com/custom_registry'], + }, + ], + }); }); it('returns maven.install and maven.artifact dependencies', async () => { @@ -263,32 +416,35 @@ describe('modules/manager/bazel-module/extract', () => { if (!result) { throw new Error('Expected a result.'); } - expect(result.deps).toEqual([ - { - datasource: MavenDatasource.id, - depType: 'maven_install', - depName: 'junit:junit', - currentValue: '4.13.2', - registryUrls: ['https://repo1.maven.org/maven2/'], - versioning: 'gradle', - }, - { - datasource: MavenDatasource.id, - depType: 'maven_install', - depName: 'com.google.guava:guava', - currentValue: '31.1-jre', - registryUrls: ['https://repo1.maven.org/maven2/'], - versioning: 'gradle', - }, - { - datasource: MavenDatasource.id, - depType: 'maven_install', - depName: 'org.clojure:core.specs.alpha', - currentValue: '0.2.56', - registryUrls: ['https://repo1.maven.org/maven2/'], - versioning: 'gradle', - }, - ]); + + expect(result).toEqual({ + deps: [ + { + datasource: MavenDatasource.id, + versioning: 'gradle', + depName: 'junit:junit', + currentValue: '4.13.2', + depType: 'maven_install', + registryUrls: ['https://repo1.maven.org/maven2/'], + }, + { + datasource: MavenDatasource.id, + versioning: 'gradle', + depName: 'com.google.guava:guava', + currentValue: '31.1-jre', + depType: 'maven_install', + registryUrls: ['https://repo1.maven.org/maven2/'], + }, + { + datasource: MavenDatasource.id, + versioning: 'gradle', + depName: 'org.clojure:core.specs.alpha', + currentValue: '0.2.56', + depType: 'maven_install', + registryUrls: ['https://repo1.maven.org/maven2/'], + }, + ], + }); }); it('returns oci.pull dependencies', async () => { @@ -306,17 +462,20 @@ describe('modules/manager/bazel-module/extract', () => { if (!result) { throw new Error('Expected a result.'); } - expect(result.deps).toEqual([ - { - datasource: DockerDatasource.id, - depType: 'oci_pull', - depName: 'nginx_image', - packageName: 'index.docker.io/library/nginx', - currentValue: '1.27.1', - currentDigest: - 'sha256:287ff321f9e3cde74b600cc26197424404157a72043226cbbf07ee8304a2c720', - }, - ]); + + expect(result).toEqual({ + deps: [ + { + datasource: DockerDatasource.id, + depType: 'oci_pull', + depName: 'nginx_image', + packageName: 'index.docker.io/library/nginx', + currentValue: '1.27.1', + currentDigest: + 'sha256:287ff321f9e3cde74b600cc26197424404157a72043226cbbf07ee8304a2c720', + }, + ], + }); }); it('returns oci.pull dependencies without tags', async () => { @@ -333,16 +492,19 @@ describe('modules/manager/bazel-module/extract', () => { if (!result) { throw new Error('Expected a result.'); } - expect(result.deps).toEqual([ - { - datasource: DockerDatasource.id, - depType: 'oci_pull', - depName: 'nginx_image', - packageName: 'index.docker.io/library/nginx', - currentDigest: - 'sha256:287ff321f9e3cde74b600cc26197424404157a72043226cbbf07ee8304a2c720', - }, - ]); + + expect(result).toEqual({ + deps: [ + { + datasource: DockerDatasource.id, + depType: 'oci_pull', + depName: 'nginx_image', + packageName: 'index.docker.io/library/nginx', + currentDigest: + 'sha256:287ff321f9e3cde74b600cc26197424404157a72043226cbbf07ee8304a2c720', + }, + ], + }); }); it('returns maven.install and bazel_dep dependencies together', async () => { @@ -367,30 +529,33 @@ describe('modules/manager/bazel-module/extract', () => { if (!result) { throw new Error('Expected a result.'); } - expect(result.deps).toEqual([ - { - datasource: BazelDatasource.id, - depType: 'bazel_dep', - depName: 'bazel_jar_jar', - currentValue: '0.1.0', - }, - { - datasource: MavenDatasource.id, - depType: 'maven_install', - depName: 'junit:junit', - currentValue: '4.13.2', - registryUrls: ['https://repo1.maven.org/maven2/'], - versioning: 'gradle', - }, - { - datasource: MavenDatasource.id, - depType: 'maven_install', - depName: 'com.google.guava:guava', - currentValue: '31.1-jre', - registryUrls: ['https://repo1.maven.org/maven2/'], - versioning: 'gradle', - }, - ]); + + expect(result).toEqual({ + deps: [ + { + datasource: BazelDatasource.id, + depType: 'bazel_dep', + depName: 'bazel_jar_jar', + currentValue: '0.1.0', + }, + { + datasource: MavenDatasource.id, + versioning: 'gradle', + depName: 'junit:junit', + currentValue: '4.13.2', + depType: 'maven_install', + registryUrls: ['https://repo1.maven.org/maven2/'], + }, + { + datasource: MavenDatasource.id, + versioning: 'gradle', + depName: 'com.google.guava:guava', + currentValue: '31.1-jre', + depType: 'maven_install', + registryUrls: ['https://repo1.maven.org/maven2/'], + }, + ], + }); }); it('returns git_repository dependencies', async () => { @@ -405,18 +570,18 @@ describe('modules/manager/bazel-module/extract', () => { if (!result) { throw new Error('Expected a result.'); } - expect(result.deps).toHaveLength(1); - expect(result.deps).toEqual( - expect.arrayContaining([ + + expect(result).toEqual({ + deps: [ { - datasource: GithubTagsDatasource.id, depType: 'git_repository', depName: 'rules_foo', currentDigest: '850cb49c8649e463b80ef7984e7c744279746170', + datasource: GithubTagsDatasource.id, packageName: 'example/rules_foo', }, - ]), - ); + ], + }); }); }); }); diff --git a/lib/modules/manager/bazel-module/rules.spec.ts b/lib/modules/manager/bazel-module/rules.spec.ts index c3a8b2a2207cef..cd035d3a867e41 100644 --- a/lib/modules/manager/bazel-module/rules.spec.ts +++ b/lib/modules/manager/bazel-module/rules.spec.ts @@ -25,6 +25,13 @@ const bazelDepPkgDep: BasePackageDep = { depName: 'rules_foo', currentValue: '1.2.3', }; +const bazelDepPkgDepNoVersion: BasePackageDep = { + datasource: BazelDatasource.id, + depType: 'bazel_dep', + depName: 'rules_foo', + currentValue: undefined, + skipReason: 'unspecified-version', +}; const gitOverrideForGithubPkgDep: OverridePackageDep = { datasource: GithubTagsDatasource.id, depType: 'git_override', @@ -94,6 +101,10 @@ describe('modules/manager/bazel-module/rules', () => { name: fragments.string('rules_foo'), version: fragments.string('1.2.3'), }); + const bazelDepWithoutDevDepNoVersion = fragments.record({ + rule: fragments.string('bazel_dep'), + name: fragments.string('rules_foo'), + }); const gitOverrideWithGihubHost = fragments.record({ rule: fragments.string('git_override'), module_name: fragments.string('rules_foo'), @@ -131,6 +142,7 @@ describe('modules/manager/bazel-module/rules', () => { it.each` msg | a | exp ${'bazel_dep'} | ${bazelDepWithoutDevDep} | ${bazelDepPkgDep} + ${'bazel_dep, no version'} | ${bazelDepWithoutDevDepNoVersion} | ${bazelDepPkgDepNoVersion} ${'git_override, GitHub host'} | ${gitOverrideWithGihubHost} | ${gitOverrideForGithubPkgDep} ${'git_override, unsupported host'} | ${gitOverrideWithUnsupportedHost} | ${gitOverrideForUnsupportedPkgDep} ${'archive_override'} | ${archiveOverride} | ${archiveOverridePkgDep} @@ -169,10 +181,17 @@ describe('modules/manager/bazel-module/rules', () => { describe('.toPackageDependencies()', () => { const expectedBazelDepNoOverrides: PackageDependency[] = [bazelDepPkgDep]; + const expectedBazelDepNoOverridesNoVersion: PackageDependency[] = [ + bazelDepPkgDepNoVersion, + ]; const expectedBazelDepAndGitOverride: PackageDependency[] = [ deepmerge(bazelDepPkgDep, { skipReason: 'git-dependency' }), bazelModulePackageDepToPackageDependency(gitOverrideForGithubPkgDep), ]; + const expectedBazelDepNoVersionAndGitOverride: PackageDependency[] = [ + deepmerge(bazelDepPkgDepNoVersion, { skipReason: 'git-dependency' }), + bazelModulePackageDepToPackageDependency(gitOverrideForGithubPkgDep), + ]; const expectedBazelDepAndSingleVersionOverride: PackageDependency[] = [ deepmerge(bazelDepPkgDep, { skipReason: 'is-pinned', @@ -180,30 +199,56 @@ describe('modules/manager/bazel-module/rules', () => { }), bazelModulePackageDepToPackageDependency(singleVersionOverridePkgDep), ]; + const expectedBazelDepNoVersionAndSingleVersionOverride: PackageDependency[] = + [ + deepmerge(bazelDepPkgDepNoVersion, { + skipReason: 'is-pinned', + registryUrls: [customRegistryUrl], + }), + bazelModulePackageDepToPackageDependency(singleVersionOverridePkgDep), + ]; const expectedBazelDepAndArchiveOverride: PackageDependency[] = [ deepmerge(bazelDepPkgDep, { skipReason: 'file-dependency' }), bazelModulePackageDepToPackageDependency(archiveOverridePkgDep), ]; + const expectedBazelDepNoVersionAndArchiveOverride: PackageDependency[] = [ + deepmerge(bazelDepPkgDepNoVersion, { skipReason: 'file-dependency' }), + bazelModulePackageDepToPackageDependency(archiveOverridePkgDep), + ]; const expectedBazelDepAndLocalPathOverride: PackageDependency[] = [ deepmerge(bazelDepPkgDep, { skipReason: 'local-dependency' }), bazelModulePackageDepToPackageDependency(localPathOverridePkgDep), ]; + const expectedBazelDepNoVersionAndLocalPathOverride: PackageDependency[] = [ + deepmerge(bazelDepPkgDepNoVersion, { skipReason: 'local-dependency' }), + bazelModulePackageDepToPackageDependency(localPathOverridePkgDep), + ]; // If a registry is specified and a version is not specified for a // single_version_override, it is merely providing a registry URL for the bazel_dep. const expectedBazelDepWithRegistry: PackageDependency[] = [ deepmerge(bazelDepPkgDep, { registryUrls: [customRegistryUrl] }), ]; + const expectedBazelDepNoVersionWithRegistry: PackageDependency[] = [ + deepmerge(bazelDepPkgDepNoVersion, { registryUrls: [customRegistryUrl] }), + ]; it.each` - msg | a | exp - ${'bazel_dep, no overrides'} | ${[bazelDepPkgDep]} | ${expectedBazelDepNoOverrides} - ${'bazel_dep & git_override'} | ${[bazelDepPkgDep, gitOverrideForGithubPkgDep]} | ${expectedBazelDepAndGitOverride} - ${'git_override, no bazel_dep'} | ${[gitOverrideForGithubPkgDep]} | ${[]} - ${'bazel_dep & archive_override'} | ${[bazelDepPkgDep, archiveOverridePkgDep]} | ${expectedBazelDepAndArchiveOverride} - ${'bazel_dep & local_path_override'} | ${[bazelDepPkgDep, localPathOverridePkgDep]} | ${expectedBazelDepAndLocalPathOverride} - ${'single_version_override, with version and registry'} | ${[bazelDepPkgDep, singleVersionOverridePkgDep]} | ${expectedBazelDepAndSingleVersionOverride} - ${'single_version_override, with registry'} | ${[bazelDepPkgDep, singleVersionOverrideWithRegistryPkgDep]} | ${expectedBazelDepWithRegistry} - ${'single_version_override, without version and registry'} | ${[bazelDepPkgDep, singleVersionOverrideWithoutVersionAndRegistryPkgDep]} | ${[bazelDepPkgDep]} + msg | a | exp + ${'bazel_dep, no overrides'} | ${[bazelDepPkgDep]} | ${expectedBazelDepNoOverrides} + ${'bazel_dep, no overrides, no version'} | ${[bazelDepPkgDepNoVersion]} | ${expectedBazelDepNoOverridesNoVersion} + ${'bazel_dep & git_override'} | ${[bazelDepPkgDep, gitOverrideForGithubPkgDep]} | ${expectedBazelDepAndGitOverride} + ${'bazel_dep, no version & git_override'} | ${[bazelDepPkgDepNoVersion, gitOverrideForGithubPkgDep]} | ${expectedBazelDepNoVersionAndGitOverride} + ${'git_override, no bazel_dep'} | ${[gitOverrideForGithubPkgDep]} | ${[]} + ${'bazel_dep & archive_override'} | ${[bazelDepPkgDep, archiveOverridePkgDep]} | ${expectedBazelDepAndArchiveOverride} + ${'bazel_dep, no version & archive_override'} | ${[bazelDepPkgDepNoVersion, archiveOverridePkgDep]} | ${expectedBazelDepNoVersionAndArchiveOverride} + ${'bazel_dep & local_path_override'} | ${[bazelDepPkgDep, localPathOverridePkgDep]} | ${expectedBazelDepAndLocalPathOverride} + ${'bazel_dep, no version & local_path_override'} | ${[bazelDepPkgDepNoVersion, localPathOverridePkgDep]} | ${expectedBazelDepNoVersionAndLocalPathOverride} + ${'single_version_override, with version and registry'} | ${[bazelDepPkgDep, singleVersionOverridePkgDep]} | ${expectedBazelDepAndSingleVersionOverride} + ${'bazel_dep, no version & single_version_override, with version and registry'} | ${[bazelDepPkgDepNoVersion, singleVersionOverridePkgDep]} | ${expectedBazelDepNoVersionAndSingleVersionOverride} + ${'single_version_override, with registry'} | ${[bazelDepPkgDep, singleVersionOverrideWithRegistryPkgDep]} | ${expectedBazelDepWithRegistry} + ${'bazel_dep, no version & single_version_override, with registry'} | ${[bazelDepPkgDepNoVersion, singleVersionOverrideWithRegistryPkgDep]} | ${expectedBazelDepNoVersionWithRegistry} + ${'single_version_override, without version and registry'} | ${[bazelDepPkgDep, singleVersionOverrideWithoutVersionAndRegistryPkgDep]} | ${[bazelDepPkgDep]} + ${'bazel_dep, no version & single_version_override, without version and registry'} | ${[bazelDepPkgDepNoVersion, singleVersionOverrideWithoutVersionAndRegistryPkgDep]} | ${[bazelDepPkgDepNoVersion]} `('with $msg', ({ msg, a, exp }) => { const result = toPackageDependencies(a); expect(result).toEqual(exp); diff --git a/lib/modules/manager/bazel-module/rules.ts b/lib/modules/manager/bazel-module/rules.ts index 8f1bb2e626d77a..ef0fe52fc6b4c3 100644 --- a/lib/modules/manager/bazel-module/rules.ts +++ b/lib/modules/manager/bazel-module/rules.ts @@ -69,14 +69,15 @@ const BazelDepToPackageDep = RecordFragmentSchema.extend({ value: z.literal('bazel_dep'), }), name: StringFragmentSchema, - version: StringFragmentSchema, + version: StringFragmentSchema.optional(), }), }).transform( ({ children: { rule, name, version } }): BasePackageDep => ({ datasource: BazelDatasource.id, depType: rule.value, depName: name.value, - currentValue: version.value, + currentValue: version?.value, + ...(version ? {} : { skipReason: 'unspecified-version' }), }), ); diff --git a/lib/modules/manager/bitbucket-pipelines/extract.spec.ts b/lib/modules/manager/bitbucket-pipelines/extract.spec.ts index 454c7673d46bf4..764eef4aa3d452 100644 --- a/lib/modules/manager/bitbucket-pipelines/extract.spec.ts +++ b/lib/modules/manager/bitbucket-pipelines/extract.spec.ts @@ -130,7 +130,8 @@ describe('modules/manager/bitbucket-pipelines/extract', () => { currentDigest: undefined, currentValue: '2.0.2', datasource: 'docker', - depName: 'some.jfrog.mirror/jfrog-setup-cli', + depName: 'jfrogecosystem/jfrog-setup-cli', + packageName: 'some.jfrog.mirror/jfrog-setup-cli', depType: 'docker', }, { diff --git a/lib/modules/manager/bitrise/extract.ts b/lib/modules/manager/bitrise/extract.ts index 8bc593e922c037..c006740cf3c823 100644 --- a/lib/modules/manager/bitrise/extract.ts +++ b/lib/modules/manager/bitrise/extract.ts @@ -1,35 +1,15 @@ -import is from '@sindresorhus/is'; import { logger } from '../../../logger'; -import { parseSingleYaml } from '../../../util/yaml'; -import type { PackageDependency, PackageFileContent } from '../types'; +import type { PackageFileContent } from '../types'; import { BitriseFile } from './schema'; -import { parseStep } from './utils'; export function extractPackageFile( content: string, packageFile: string, ): PackageFileContent | null { - const deps: PackageDependency[] = []; - - try { - const parsed = parseSingleYaml(content, { - customSchema: BitriseFile, - }); - - const workflows = Object.values(parsed.workflows); - for (const workflow of workflows) { - const steps = workflow.steps.flatMap((step) => Object.keys(step)); - for (const step of steps) { - const dep = parseStep(step, parsed.default_step_lib_source); - - if (!is.nullOrUndefined(dep)) { - deps.push(dep); - } - } - } - } catch (err) { + const deps = BitriseFile.catch(({ error: err }) => { logger.debug({ err, packageFile }, `Failed to parse Bitrise YAML config`); - } + return []; + }).parse(content); if (!deps.length) { return null; diff --git a/lib/modules/manager/bitrise/schema.ts b/lib/modules/manager/bitrise/schema.ts index f32030d93dc966..02c1d037a27b77 100644 --- a/lib/modules/manager/bitrise/schema.ts +++ b/lib/modules/manager/bitrise/schema.ts @@ -1,12 +1,25 @@ import { z } from 'zod'; +import { filterMap } from '../../../util/filter-map'; +import { Yaml } from '../../../util/schema-utils'; +import { parseStep } from './utils'; -export const BitriseStep = z.record(z.string(), z.unknown()); - -export const BitriseWorkflow = z.object({ - steps: z.array(BitriseStep), -}); - -export const BitriseFile = z.object({ - default_step_lib_source: z.string().optional(), - workflows: z.record(z.string(), BitriseWorkflow), -}); +export const BitriseFile = Yaml.pipe( + z + .object({ + default_step_lib_source: z.string().optional(), + workflows: z + .record( + z + .object({ + steps: z + .array(z.record(z.unknown()).transform((x) => Object.keys(x))) + .transform((steps) => steps.flat()), + }) + .transform(({ steps }) => steps), + ) + .transform((x) => Object.values(x).flat()), + }) + .transform(({ default_step_lib_source: defaultRegistry, workflows }) => + filterMap(workflows, (workflow) => parseStep(workflow, defaultRegistry)), + ), +); diff --git a/lib/modules/manager/buildpacks/extract.spec.ts b/lib/modules/manager/buildpacks/extract.spec.ts index dedcefcbd4f68a..5f70c39e0d1866 100644 --- a/lib/modules/manager/buildpacks/extract.spec.ts +++ b/lib/modules/manager/buildpacks/extract.spec.ts @@ -23,6 +23,7 @@ describe('modules/manager/buildpacks/extract', () => { [_] schema-version = "0.2" +# valid cases [io.buildpacks] builder = "registry.corp/builder/noble:1.1.1" @@ -36,16 +37,22 @@ uri = "buildpacks/nodejs:3.3.3" uri = "example/foo@1.0.0" [[io.buildpacks.group]] -uri = "example/registry-cnb" +uri = "urn:cnb:registry:example/bar@1.2.3" [[io.buildpacks.group]] -uri = "urn:cnb:registry:example/foo@1.0.0" +uri = "cnbs/some-bp@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" [[io.buildpacks.group]] -uri = "some-bp@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +uri = "cnbs/some-bp:some-tag@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" [[io.buildpacks.group]] -uri = "cnbs/some-bp:some-tag@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +id = "example/tee" +version = "2.3.4" + +#invalid cases + +[[io.buildpacks.group]] +uri = "example/registry-cnb" [[io.buildpacks.group]] uri = "from=builder:foobar" @@ -54,7 +61,10 @@ uri = "from=builder:foobar" uri = "file://local.oci" [[io.buildpacks.group]] -uri = "foo://fake.oci"`, +uri = "foo://fake.oci" + +[[io.buildpacks.group]] +id = "not/valid"`, 'project.toml', {}, ); @@ -66,6 +76,7 @@ uri = "foo://fake.oci"`, currentValue: '1.1.1', datasource: 'docker', depName: 'registry.corp/builder/noble', + packageName: 'registry.corp/builder/noble', replaceString: 'registry.corp/builder/noble:1.1.1', }, { @@ -74,6 +85,7 @@ uri = "foo://fake.oci"`, currentValue: '2.2.2', datasource: 'docker', depName: 'buildpacks/java', + packageName: 'buildpacks/java', replaceString: 'buildpacks/java:2.2.2', }, { @@ -82,17 +94,33 @@ uri = "foo://fake.oci"`, currentValue: '3.3.3', datasource: 'docker', depName: 'buildpacks/nodejs', + packageName: 'buildpacks/nodejs', replaceString: 'buildpacks/nodejs:3.3.3', }, + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + datasource: 'buildpacks-registry', + currentValue: '1.0.0', + packageName: 'example/foo', + }, + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + datasource: 'buildpacks-registry', + currentValue: '1.2.3', + packageName: 'example/bar', + }, { autoReplaceStringTemplate: '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', currentDigest: 'sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', datasource: 'docker', - depName: 'some-bp', + depName: 'cnbs/some-bp', + packageName: 'cnbs/some-bp', replaceString: - 'some-bp@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + 'cnbs/some-bp@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', }, { autoReplaceStringTemplate: @@ -102,9 +130,15 @@ uri = "foo://fake.oci"`, currentValue: 'some-tag', datasource: 'docker', depName: 'cnbs/some-bp', + packageName: 'cnbs/some-bp', replaceString: 'cnbs/some-bp:some-tag@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', }, + { + datasource: 'buildpacks-registry', + currentValue: '2.3.4', + packageName: 'example/tee', + }, ]); }); }); diff --git a/lib/modules/manager/buildpacks/extract.ts b/lib/modules/manager/buildpacks/extract.ts index c00d4a4c681e19..f723e6ddf2999c 100644 --- a/lib/modules/manager/buildpacks/extract.ts +++ b/lib/modules/manager/buildpacks/extract.ts @@ -1,13 +1,20 @@ import is from '@sindresorhus/is'; import { logger } from '../../../logger'; import { regEx } from '../../../util/regex'; +import { BuildpacksRegistryDatasource } from '../../datasource/buildpacks-registry'; +import { isVersion } from '../../versioning/semver'; import { getDep } from '../dockerfile/extract'; import type { ExtractConfig, PackageDependency, PackageFileContent, } from '../types'; -import { type ProjectDescriptor, ProjectDescriptorToml } from './schema'; +import { + type ProjectDescriptor, + ProjectDescriptorToml, + isBuildpackByName, + isBuildpackByURI, +} from './schema'; const dockerPrefix = regEx(/^docker:\/?\//); const dockerRef = regEx( @@ -20,6 +27,24 @@ function isDockerRef(ref: string): boolean { } return false; } +const buildpackRegistryPrefix = 'urn:cnb:registry:'; +const buildpackRegistryId = regEx( + /^[a-z0-9\-.]+\/[a-z0-9\-.]+(?:@(?.+))?$/, +); + +function isBuildpackRegistryId(ref: string): boolean { + const bpRegistryMatch = buildpackRegistryId.exec(ref); + if (!bpRegistryMatch) { + return false; + } else if (!bpRegistryMatch.groups?.version) { + return true; + } + return isVersion(bpRegistryMatch.groups.version); +} + +function isBuildpackRegistryRef(ref: string): boolean { + return isBuildpackRegistryId(ref) || ref.startsWith(buildpackRegistryPrefix); +} function parseProjectToml( content: string, @@ -76,7 +101,7 @@ export function extractPackageFile( is.array(descriptor.io.buildpacks.group) ) { for (const group of descriptor.io.buildpacks.group) { - if (group.uri && isDockerRef(group.uri)) { + if (isBuildpackByURI(group) && isDockerRef(group.uri)) { const dep = getDep( group.uri.replace(dockerPrefix, ''), true, @@ -92,6 +117,31 @@ export function extractPackageFile( ); deps.push(dep); + } else if (isBuildpackByURI(group) && isBuildpackRegistryRef(group.uri)) { + const dependency = group.uri.replace(buildpackRegistryPrefix, ''); + + if (dependency.includes('@')) { + const version = dependency.split('@')[1]; + const dep: PackageDependency = { + datasource: BuildpacksRegistryDatasource.id, + currentValue: version, + packageName: dependency.split('@')[0], + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + }; + deps.push(dep); + } + } else if (isBuildpackByName(group)) { + const version = group.version; + + if (version) { + const dep: PackageDependency = { + datasource: BuildpacksRegistryDatasource.id, + currentValue: version, + packageName: group.id, + }; + deps.push(dep); + } } } } diff --git a/lib/modules/manager/buildpacks/index.ts b/lib/modules/manager/buildpacks/index.ts index 53a3dcfeb4c221..869bcd7252b86c 100644 --- a/lib/modules/manager/buildpacks/index.ts +++ b/lib/modules/manager/buildpacks/index.ts @@ -1,4 +1,5 @@ import type { Category } from '../../../constants'; +import { BuildpacksRegistryDatasource } from '../../datasource/buildpacks-registry'; import { DockerDatasource } from '../../datasource/docker'; export { extractPackageFile } from './extract'; @@ -8,5 +9,8 @@ export const defaultConfig = { pinDigests: false, }; -export const categories: Category[] = ['docker']; -export const supportedDatasources = [DockerDatasource.id]; +export const categories: Category[] = ['docker', 'ci', 'cd']; +export const supportedDatasources = [ + DockerDatasource.id, + BuildpacksRegistryDatasource.id, +]; diff --git a/lib/modules/manager/buildpacks/readme.md b/lib/modules/manager/buildpacks/readme.md index d2f7b6c613968a..b25340bffc2431 100644 --- a/lib/modules/manager/buildpacks/readme.md +++ b/lib/modules/manager/buildpacks/readme.md @@ -6,7 +6,7 @@ Renovate can update a `project.toml` file if: - The file follows the [project descriptor specifications](https://github.com/buildpacks/spec/blob/main/extensions/project-descriptor.md) - The buildpack `uri` is an OCI image reference (references to a local file or buildpack registry are ignored) -If you use buildpacks in the `io.buildpacks.group` array, then you _must_ configure the Docker reference (`uri`) for Renovate to work. +**Note**: If you use buildpacks in the `io.buildpacks.group` array, then you _must_ configure the Docker reference (`uri`) for Renovate to work. ```toml title="Example of a project.toml file with Docker reference URIs" [_] diff --git a/lib/modules/manager/buildpacks/schema.ts b/lib/modules/manager/buildpacks/schema.ts index dc90371ca4ce93..15d5b54fa77c4c 100644 --- a/lib/modules/manager/buildpacks/schema.ts +++ b/lib/modules/manager/buildpacks/schema.ts @@ -1,10 +1,33 @@ import { z } from 'zod'; import { Toml } from '../../../util/schema-utils'; -const BuildpackGroup = z.object({ - uri: z.string().optional(), +const BuildpackByName = z.object({ + id: z.string(), + version: z.string().optional(), }); +const BuildpackByURI = z.object({ + uri: z.string(), +}); + +const BuildpackGroup = BuildpackByName.or(BuildpackByURI); + +type BuildpackByName = z.infer; +type BuildpackByURI = z.infer; +type BuildpackGroup = z.infer; + +export function isBuildpackByName( + group: BuildpackGroup, +): group is BuildpackByName { + return 'id' in group; +} + +export function isBuildpackByURI( + group: BuildpackGroup, +): group is BuildpackByURI { + return 'uri' in group; +} + const IoBuildpacks = z.object({ builder: z.string().optional(), group: z.array(BuildpackGroup).optional(), diff --git a/lib/modules/manager/bundler/artifacts.spec.ts b/lib/modules/manager/bundler/artifacts.spec.ts index ad0ee829c84efb..857afb0b661711 100644 --- a/lib/modules/manager/bundler/artifacts.spec.ts +++ b/lib/modules/manager/bundler/artifacts.spec.ts @@ -679,71 +679,6 @@ describe('modules/manager/bundler/artifacts', () => { ]); }); - it('handles failure of strict updating for version solving', async () => { - const execError = new ExecError('Exec error', { - cmd: '', - stdout: '', - stderr: 'version solving has failed', - options: { encoding: 'utf8' }, - }); - fs.readLocalFile.mockResolvedValue('Current Gemfile.lock'); - const execSnapshots = mockExecSequence([ - execError, - { stdout: '', stderr: '' }, - ]); - git.getRepoStatus.mockResolvedValueOnce( - partial({ - modified: ['Gemfile.lock'], - }), - ); - - const res = await updateArtifacts({ - packageFileName: 'Gemfile', - updatedDeps: [{ depName: 'foo', updateType: 'minor' }], - newPackageFileContent: '{}', - config, - }); - - expect(res).toMatchObject([{ file: { path: 'Gemfile.lock' } }]); - expect(execSnapshots).toMatchObject([ - { cmd: 'bundler lock --minor --strict --update foo' }, - { cmd: 'bundler lock --minor --conservative --update foo' }, - ]); - }); - - it('handles failure of strict updating for missing gem', async () => { - // See https://github.com/rubygems/rubygems/issues/7369 - const execError = new ExecError('Exec error', { - cmd: '', - stdout: '', - stderr: "Could not find gems matching 'foo ", - options: { encoding: 'utf8' }, - }); - fs.readLocalFile.mockResolvedValue('Current Gemfile.lock'); - const execSnapshots = mockExecSequence([ - execError, - { stdout: '', stderr: '' }, - ]); - git.getRepoStatus.mockResolvedValueOnce( - partial({ - modified: ['Gemfile.lock'], - }), - ); - - const res = await updateArtifacts({ - packageFileName: 'Gemfile', - updatedDeps: [{ depName: 'foo', updateType: 'minor' }], - newPackageFileContent: '{}', - config, - }); - - expect(res).toMatchObject([{ file: { path: 'Gemfile.lock' } }]); - expect(execSnapshots).toMatchObject([ - { cmd: 'bundler lock --minor --strict --update foo' }, - { cmd: 'bundler lock --minor --conservative --update foo' }, - ]); - }); - it('updates the Gemfile.lock when upgrading ruby', async () => { // See https://github.com/renovatebot/renovate/issues/15114 fs.readLocalFile.mockResolvedValue('Current Gemfile.lock'); diff --git a/lib/modules/manager/bundler/artifacts.ts b/lib/modules/manager/bundler/artifacts.ts index 071bd1a652c2ce..6dd081147cbba9 100644 --- a/lib/modules/manager/bundler/artifacts.ts +++ b/lib/modules/manager/bundler/artifacts.ts @@ -105,8 +105,8 @@ export async function updateArtifacts( } const updateTypes = { - patch: '--patch --strict ', - minor: '--minor --strict ', + patch: '--patch ', + minor: '--minor ', major: '', }; for (const [updateType, updateArg] of Object.entries(updateTypes)) { @@ -120,12 +120,9 @@ export async function updateArtifacts( additionalArgs = '--conservative '; } if (deps.length) { - let cmd = `bundler lock ${updateArg}${additionalArgs}--update ${deps + const cmd = `bundler lock ${updateArg}${additionalArgs}--update ${deps .map(quote) .join(' ')}`; - if (cmd.includes(' --conservative ')) { - cmd = cmd.replace(' --strict', ''); - } commands.push(cmd); } } @@ -226,29 +223,6 @@ export async function updateArtifacts( memCache.set('bundlerArtifactsError', BUNDLER_INVALID_CREDENTIALS); throw new Error(BUNDLER_INVALID_CREDENTIALS); } - if ( - recursionLimit > 0 && - (output.includes('version solving has failed') || - output.includes('Could not find gem')) - ) { - logger.debug('Failed to lock strictly, retrying non-strict'); - const newConfig = { - ...config, - postUpdateOptions: [ - ...(config.postUpdateOptions ?? []), - 'bundlerConservative', - ], - }; - return updateArtifacts( - { - packageFileName, - updatedDeps, - newPackageFileContent, - config: newConfig, - }, - recursionLimit - 1, - ); - } const resolveMatches: string[] = getResolvedPackages(output).filter( (depName) => !updatedDepNames.includes(depName), ); diff --git a/lib/modules/manager/cargo/artifacts.ts b/lib/modules/manager/cargo/artifacts.ts index 8ac570605d2008..4d144f83fb353a 100644 --- a/lib/modules/manager/cargo/artifacts.ts +++ b/lib/modules/manager/cargo/artifacts.ts @@ -135,7 +135,8 @@ async function updateArtifactsImpl( // If there is a dependency without a locked version then log a warning // and perform a regular workspace lockfile update. logger.warn( - `Missing locked version for dependency \`${missingDep.depName}\``, + { dependency: missingDep.depName }, + 'Missing locked version for dependency', ); await cargoUpdate( packageFileName, diff --git a/lib/modules/manager/cargo/extract.ts b/lib/modules/manager/cargo/extract.ts index 3d090e52b7a3e8..35e9d6926bfbad 100644 --- a/lib/modules/manager/cargo/extract.ts +++ b/lib/modules/manager/cargo/extract.ts @@ -134,7 +134,7 @@ function resolveRegistryIndex( `Replacing index of cargo registry ${registryName} with ${replacementName}`, ); if (originalNames.has(replacementName)) { - logger.warn(`${registryName} cargo registry resolves to itself`); + logger.warn({ registryName }, 'cargo registry resolves to itself'); return null; } return resolveRegistryIndex( diff --git a/lib/modules/manager/circleci/extract.spec.ts b/lib/modules/manager/circleci/extract.spec.ts index 25915c53639f84..dc0d97a6485767 100644 --- a/lib/modules/manager/circleci/extract.spec.ts +++ b/lib/modules/manager/circleci/extract.spec.ts @@ -32,7 +32,8 @@ describe('modules/manager/circleci/extract', () => { currentDigest: undefined, currentValue: '0.6.2', datasource: 'docker', - depName: 'my-quay-mirror.registry.com/myName/myPackage', + depName: 'quay.io/myName/myPackage', + packageName: 'my-quay-mirror.registry.com/myName/myPackage', depType: 'docker', replaceString: 'quay.io/myName/myPackage:0.6.2', }, @@ -48,6 +49,7 @@ describe('modules/manager/circleci/extract', () => { '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', datasource: 'docker', depName: 'node', + packageName: 'node', depType: 'docker', replaceString: 'node', }, @@ -57,6 +59,7 @@ describe('modules/manager/circleci/extract', () => { currentValue: '4', datasource: 'docker', depName: 'node', + packageName: 'node', depType: 'docker', replaceString: 'node:4', }, @@ -66,6 +69,7 @@ describe('modules/manager/circleci/extract', () => { currentValue: '6', datasource: 'docker', depName: 'node', + packageName: 'node', depType: 'docker', replaceString: 'node:6', }, @@ -75,6 +79,7 @@ describe('modules/manager/circleci/extract', () => { currentValue: '8.9.0', datasource: 'docker', depName: 'node', + packageName: 'node', depType: 'docker', replaceString: 'node:8.9.0', }, @@ -115,6 +120,7 @@ describe('modules/manager/circleci/extract', () => { currentValue: '3.7', datasource: 'docker', depName: 'python', + packageName: 'python', depType: 'docker', replaceString: 'python:3.7@sha256:3870d35b962a943df72d948580fc66ceaaee1c4fbd205930f32e0f0760eb1077', @@ -127,6 +133,7 @@ describe('modules/manager/circleci/extract', () => { currentValue: '3.7', datasource: 'docker', depName: 'python', + packageName: 'python', depType: 'docker', replaceString: 'python:3.7@sha256:3870d35b962a943df72d948580fc66ceaaee1c4fbd205930f32e0f0760eb1077', @@ -139,6 +146,7 @@ describe('modules/manager/circleci/extract', () => { currentValue: '3.7', datasource: 'docker', depName: 'python', + packageName: 'python', depType: 'docker', replaceString: 'python:3.7@sha256:3870d35b962a943df72d948580fc66ceaaee1c4fbd205930f32e0f0760eb1077', @@ -151,6 +159,7 @@ describe('modules/manager/circleci/extract', () => { currentValue: '3.7', datasource: 'docker', depName: 'python', + packageName: 'python', depType: 'docker', replaceString: 'python:3.7@sha256:3870d35b962a943df72d948580fc66ceaaee1c4fbd205930f32e0f0760eb1077', @@ -163,6 +172,7 @@ describe('modules/manager/circleci/extract', () => { currentValue: '3-6', datasource: 'docker', depName: 'pypy', + packageName: 'pypy', depType: 'docker', replaceString: 'pypy:3-6@sha256:eb6325b75c1c70b4992eaa1bdd29e24e5f14d5324b4714a49f3e67783473214b', @@ -175,6 +185,7 @@ describe('modules/manager/circleci/extract', () => { currentValue: '3.7', datasource: 'docker', depName: 'python', + packageName: 'python', depType: 'docker', replaceString: 'python:3.7@sha256:3870d35b962a943df72d948580fc66ceaaee1c4fbd205930f32e0f0760eb1077', @@ -191,6 +202,7 @@ describe('modules/manager/circleci/extract', () => { currentValue: '14.8.0', datasource: 'docker', depName: 'cimg/node', + packageName: 'cimg/node', depType: 'docker', replaceString: 'cimg/node:14.8.0', }, @@ -200,6 +212,7 @@ describe('modules/manager/circleci/extract', () => { currentValue: '14.8.0', datasource: 'docker', depName: 'cimg/node', + packageName: 'cimg/node', depType: 'docker', replaceString: 'cimg/node:14.8.0', }, @@ -244,6 +257,7 @@ describe('modules/manager/circleci/extract', () => { currentValue: '3.0.3-browsers', datasource: 'docker', depName: 'cimg/ruby', + packageName: 'cimg/ruby', depType: 'docker', replaceString: 'cimg/ruby:3.0.3-browsers', }, @@ -293,6 +307,7 @@ describe('modules/manager/circleci/extract', () => { currentValue: '3.9', datasource: 'docker', depName: 'cimg/python', + packageName: 'cimg/python', depType: 'docker', replaceString: 'cimg/python:3.9', }, @@ -303,6 +318,7 @@ describe('modules/manager/circleci/extract', () => { currentValue: '3.7', datasource: 'docker', depName: 'cimg/python', + packageName: 'cimg/python', depType: 'docker', replaceString: 'cimg/python:3.7', }, diff --git a/lib/modules/manager/cloudbuild/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/cloudbuild/__snapshots__/extract.spec.ts.snap index 571bcea29b60e2..4b89bef9ad29da 100644 --- a/lib/modules/manager/cloudbuild/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/cloudbuild/__snapshots__/extract.spec.ts.snap @@ -8,6 +8,7 @@ exports[`modules/manager/cloudbuild/extract extractPackageFile() extracts multip "currentValue": "19.03.8", "datasource": "docker", "depName": "gcr.io/cloud-builders/docker", + "packageName": "gcr.io/cloud-builders/docker", "replaceString": "gcr.io/cloud-builders/docker:19.03.8", }, { @@ -16,6 +17,7 @@ exports[`modules/manager/cloudbuild/extract extractPackageFile() extracts multip "currentValue": "12", "datasource": "docker", "depName": "node", + "packageName": "node", "replaceString": "node:12", }, { @@ -24,6 +26,7 @@ exports[`modules/manager/cloudbuild/extract extractPackageFile() extracts multip "currentValue": undefined, "datasource": "docker", "depName": "gcr.io/cloud-builders/kubectl", + "packageName": "gcr.io/cloud-builders/kubectl", "replaceString": "gcr.io/cloud-builders/kubectl", }, ] diff --git a/lib/modules/manager/cloudbuild/extract.ts b/lib/modules/manager/cloudbuild/extract.ts index 92f08454da77de..9fa9500fe1a160 100644 --- a/lib/modules/manager/cloudbuild/extract.ts +++ b/lib/modules/manager/cloudbuild/extract.ts @@ -1,49 +1,25 @@ -import is from '@sindresorhus/is'; import { logger } from '../../../logger'; -import { parseSingleYaml } from '../../../util/yaml'; import { getDep } from '../dockerfile/extract'; -import type { PackageDependency, PackageFileContent } from '../types'; +import type { PackageFileContent } from '../types'; +import { CloudbuildSteps } from './schema'; export function extractPackageFile( content: string, packageFile?: string, ): PackageFileContent | null { - const deps: PackageDependency[] = []; - try { - // TODO: fix types - const doc: any = parseSingleYaml(content); - if (doc?.steps && is.array(doc.steps)) { - for (const step of doc.steps) { - if (step.name) { - const dep = getDep(step.name); - logger.trace( - { - depName: dep.depName, - currentValue: dep.currentValue, - currentDigest: dep.currentDigest, - }, - 'Cloud Build docker image', - ); + const deps = CloudbuildSteps.catch(({ error: err }) => { + logger.debug( + { err, packageFile }, + 'Cloud Build: error extracting Docker images from a configuration file.', + ); + return []; + }) + .transform((steps) => steps.map((step) => getDep(step))) + .parse(content); - deps.push(dep); - } - } - } - } catch (err) /* istanbul ignore next */ { - if (err.stack?.startsWith('YAMLException:')) { - logger.debug( - { err, packageFile }, - 'YAML exception extracting Docker images from a Cloud Build configuration file.', - ); - } else { - logger.debug( - { err, packageFile }, - 'Error extracting Docker images from a Cloud Build configuration file.', - ); - } - } if (!deps.length) { return null; } + return { deps }; } diff --git a/lib/modules/manager/cloudbuild/schema.ts b/lib/modules/manager/cloudbuild/schema.ts new file mode 100644 index 00000000000000..46cad9b327457c --- /dev/null +++ b/lib/modules/manager/cloudbuild/schema.ts @@ -0,0 +1,12 @@ +import { z } from 'zod'; +import { LooseArray, Yaml } from '../../../util/schema-utils'; + +export const CloudbuildSteps = Yaml.pipe( + z + .object({ + steps: LooseArray( + z.object({ name: z.string() }).transform(({ name }) => name), + ), + }) + .transform(({ steps }) => steps), +); diff --git a/lib/modules/manager/cocoapods/extract.ts b/lib/modules/manager/cocoapods/extract.ts index 041c2a8a0aace5..50faed7450ac8b 100644 --- a/lib/modules/manager/cocoapods/extract.ts +++ b/lib/modules/manager/cocoapods/extract.ts @@ -36,12 +36,12 @@ export function parseLine(line: string): ParsedLine { const depName = result.subspec ? `${result.spec}/${result.subspec}` : result.spec; - const groupName = result.spec; + const specName = result.spec; if (depName) { result.depName = depName; } - if (groupName) { - result.groupName = groupName; + if (specName) { + result.specName = specName; } delete result.spec; delete result.subspec; @@ -96,7 +96,7 @@ export async function extractPackageFile( const parsedLine = parseLine(line); const { depName, - groupName, + specName, currentValue, git, tag, @@ -112,14 +112,14 @@ export async function extractPackageFile( const managerData = { lineNumber }; let dep: PackageDependency = { depName, - groupName, + sharedVariableName: specName, skipReason: 'unspecified-version', }; if (currentValue) { dep = { depName, - groupName, + sharedVariableName: specName, datasource: PodDatasource.id, currentValue, managerData, @@ -131,14 +131,14 @@ export async function extractPackageFile( } else { dep = { depName, - groupName, + sharedVariableName: specName, skipReason: 'git-dependency', }; } } else if (path) { dep = { depName, - groupName, + sharedVariableName: specName, skipReason: 'path-dependency', }; } diff --git a/lib/modules/manager/cocoapods/types.ts b/lib/modules/manager/cocoapods/types.ts index 74fef13b828c8f..311fb8d867ca32 100644 --- a/lib/modules/manager/cocoapods/types.ts +++ b/lib/modules/manager/cocoapods/types.ts @@ -1,6 +1,6 @@ export interface ParsedLine { depName?: string; - groupName?: string; + specName?: string; spec?: string; subspec?: string; currentValue?: string; diff --git a/lib/modules/manager/composer/schema.ts b/lib/modules/manager/composer/schema.ts index e1d9ffa47fd455..4fabd2eaa4fb2f 100644 --- a/lib/modules/manager/composer/schema.ts +++ b/lib/modules/manager/composer/schema.ts @@ -2,7 +2,12 @@ import { z } from 'zod'; import { logger } from '../../../logger'; import { readLocalFile } from '../../../util/fs'; import { regEx } from '../../../util/regex'; -import { Json, LooseArray, LooseRecord } from '../../../util/schema-utils'; +import { + Json, + LooseArray, + LooseRecord, + withDebugMessage, +} from '../../../util/schema-utils'; import { BitbucketTagsDatasource } from '../../datasource/bitbucket-tags'; import { GitTagsDatasource } from '../../datasource/git-tags'; import { GithubTagsDatasource } from '../../datasource/github-tags'; @@ -126,10 +131,7 @@ export type ReposArray = z.infer; export const Repos = z .union([ReposRecord, ReposArray]) .default([]) // Prevents warnings for packages without repositories field - .catch(({ error: err }) => { - logger.debug({ err }, 'Composer: invalid "repositories" field'); - return []; - }) + .catch(withDebugMessage([], 'Composer: invalid "repositories" field')) .transform((repos) => { let packagist = true; const repoUrls: string[] = []; @@ -242,10 +244,9 @@ export const ComposerExtract = z .pipe(Json) .pipe(Lockfile) .nullable() - .catch(({ error: err }) => { - logger.debug({ err }, 'Composer: lockfile parsing error'); - return null; - }), + .catch( + withDebugMessage(null, 'Composer: lockfile parsing error'), + ), ]), ), }), diff --git a/lib/modules/manager/crossplane/extract.spec.ts b/lib/modules/manager/crossplane/extract.spec.ts index dc16cb1962d430..d66c5baa9f7a67 100644 --- a/lib/modules/manager/crossplane/extract.spec.ts +++ b/lib/modules/manager/crossplane/extract.spec.ts @@ -102,6 +102,7 @@ describe('modules/manager/crossplane/extract', () => { currentValue: 'v0.2.0', datasource: 'docker', depName: 'xpkg.upbound.io/crossplane-contrib/provider-nop', + packageName: 'xpkg.upbound.io/crossplane-contrib/provider-nop', depType: 'provider', replaceString: 'xpkg.upbound.io/crossplane-contrib/provider-nop:v0.2.0', @@ -113,6 +114,7 @@ describe('modules/manager/crossplane/extract', () => { currentValue: 'v0.2.1', datasource: 'docker', depName: 'xpkg.upbound.io/crossplane-contrib/function-dummy', + packageName: 'xpkg.upbound.io/crossplane-contrib/function-dummy', depType: 'function', replaceString: 'xpkg.upbound.io/crossplane-contrib/function-dummy:v0.2.1', @@ -124,6 +126,7 @@ describe('modules/manager/crossplane/extract', () => { currentValue: 'v0.6.0', datasource: 'docker', depName: 'xpkg.upbound.io/upbound/platform-ref-aws', + packageName: 'xpkg.upbound.io/upbound/platform-ref-aws', depType: 'configuration', replaceString: 'xpkg.upbound.io/upbound/platform-ref-aws:v0.6.0', }, @@ -142,6 +145,7 @@ describe('modules/manager/crossplane/extract', () => { currentValue: 'v0.2.0', datasource: 'docker', depName: 'xpkg.upbound.io/crossplane-contrib/provider-nop', + packageName: 'xpkg.upbound.io/crossplane-contrib/provider-nop', depType: 'provider', replaceString: 'xpkg.upbound.io/crossplane-contrib/provider-nop:v0.2.0', diff --git a/lib/modules/manager/custom/api.ts b/lib/modules/manager/custom/api.ts index de5e051ca72ca1..f7dc64aaef3cc6 100644 --- a/lib/modules/manager/custom/api.ts +++ b/lib/modules/manager/custom/api.ts @@ -1,7 +1,9 @@ import type { ManagerApi } from '../types'; +import * as jsonata from './jsonata'; import * as regex from './regex'; const api = new Map(); export default api; api.set('regex', regex); +api.set('jsonata', jsonata); diff --git a/lib/modules/manager/custom/index.spec.ts b/lib/modules/manager/custom/index.spec.ts index 54e922bd1ec517..61a6466dbcd447 100644 --- a/lib/modules/manager/custom/index.spec.ts +++ b/lib/modules/manager/custom/index.spec.ts @@ -10,6 +10,8 @@ describe('modules/manager/custom/index', () => { expect(customManager.isCustomManager('npm')).toBe(false); expect(customManager.isCustomManager('regex')).toBe(true); expect(customManager.isCustomManager('custom.regex')).toBe(false); + expect(customManager.isCustomManager('jsonata')).toBe(true); + expect(customManager.isCustomManager('custom.jsonata')).toBe(false); }); }); }); diff --git a/lib/modules/manager/custom/jsonata/index.spec.ts b/lib/modules/manager/custom/jsonata/index.spec.ts new file mode 100644 index 00000000000000..76f964a5c5f108 --- /dev/null +++ b/lib/modules/manager/custom/jsonata/index.spec.ts @@ -0,0 +1,330 @@ +import { codeBlock } from 'common-tags'; +import { logger } from '../../../../../test/util'; +import type { JsonataExtractConfig } from './types'; +import { defaultConfig, extractPackageFile } from '.'; + +describe('modules/manager/custom/jsonata/index', () => { + it('has default config', () => { + expect(defaultConfig).toEqual({ + pinDigests: false, + }); + }); + + it('returns null when content does not match specified file format', async () => { + const res = await extractPackageFile('not-json', 'foo-file', { + fileFormat: 'json', + } as JsonataExtractConfig); + expect(res).toBeNull(); + expect(logger.logger.debug).toHaveBeenCalledWith( + expect.anything(), + 'Error while parsing file', + ); + }); + + it('returns null when no content', async () => { + const res = await extractPackageFile('', 'foo-file', { + fileFormat: 'json', + matchStrings: [ + 'packages.{ "depName": package, "currentValue": version, "versioning ": versioning }', + ], + } as JsonataExtractConfig); + expect(res).toBeNull(); + }); + + it('extracts data when no templates are used', async () => { + const json = codeBlock` + { + "packages": [ + { + "dep_name": "foo", + "package_name": "fii", + "current_value": "1.2.3", + "current_digest": "1234", + "data_source": "nuget", + "versioning": "maven", + "extract_version": "custom-extract-version", + "registry_url": "https://registry.npmjs.org", + "dep_type": "dev" + } + ] + }`; + const config = { + fileFormat: 'json', + matchStrings: [ + `packages.{ + "depName": dep_name, + "packageName": package_name, + "currentValue": current_value, + "currentDigest": current_digest, + "datasource": data_source, + "versioning": versioning, + "extractVersion": extract_version, + "registryUrl": registry_url, + "depType": dep_type + }`, + ], + }; + const res = await extractPackageFile(json, 'unused', config); + + expect(res).toMatchObject({ + deps: [ + { + depName: 'foo', + packageName: 'fii', + currentValue: '1.2.3', + currentDigest: '1234', + datasource: 'nuget', + versioning: 'maven', + extractVersion: 'custom-extract-version', + registryUrls: ['https://registry.npmjs.org/'], + depType: 'dev', + }, + ], + }); + }); + + it('extracts yaml', async () => { + const json = codeBlock` + --- + packages: + - + "dep_name": "foo" + "package_name": "fii" + "current_value": "1.2.3" + "current_digest": "1234" + "data_source": "nuget" + "versioning": "maven" + "extract_version": "custom-extract-version" + "registry_url": "https://registry.npmjs.org" + "dep_type": "dev" + --- + some: true + `; + const config = { + fileFormat: 'yaml', + matchStrings: [ + `packages.{ + "depName": dep_name, + "packageName": package_name, + "currentValue": current_value, + "currentDigest": current_digest, + "datasource": data_source, + "versioning": versioning, + "extractVersion": extract_version, + "registryUrl": registry_url, + "depType": dep_type + }`, + ], + }; + const res = await extractPackageFile(json, 'unused', config); + + expect(res).toMatchObject({ + deps: [ + { + depName: 'foo', + packageName: 'fii', + currentValue: '1.2.3', + currentDigest: '1234', + datasource: 'nuget', + versioning: 'maven', + extractVersion: 'custom-extract-version', + registryUrls: ['https://registry.npmjs.org/'], + depType: 'dev', + }, + ], + }); + }); + + it('applies templates', async () => { + const json = codeBlock` + { + "packages": [ + { + "dep_name": "foo", + "package_name": "fii", + "current_value": "1.2.3", + "current_digest": "1234", + "data_source": "nuget", + "versioning": "maven", + "extract_version": "custom-extract-version", + "registry_url": "https://registry.npmjs.org", + "dep_type": "dev" + }, + { + }] + }`; + const config = { + fileFormat: 'json', + matchStrings: [ + `packages.{ + "depName": dep_name, + "packageName": package_name, + "currentValue": current_value, + "currentDigest": current_digest, + "datasource": data_source, + "versioning": versioning, + "extractVersion": extract_version, + "registryUrl": registry_url, + "depType": dep_type + }`, + ], + depNameTemplate: + '{{#if depName}}{{depName}}{{else}}default-dep-name{{/if}}', + packageNameTemplate: + '{{#if packageName}}{{packageName}}{{else}}default-package-name{{/if}}', + currentValueTemplate: + '{{#if currentValue}}{{currentValue}}{{else}}default-current-value{{/if}}', + currentDigestTemplate: + '{{#if currentDigest}}{{currentDigest}}{{else}}default-current-digest{{/if}}', + datasourceTemplate: + '{{#if datasource}}{{datasource}}{{else}}default-datasource{{/if}}', + versioningTemplate: + '{{#if versioning}}{{versioning}}{{else}}default-versioning{{/if}}', + extractVersionTemplate: + '{{#if extractVersion}}{{extractVersion}}{{else}}default-extract-version{{/if}}', + registryUrlTemplate: + '{{#if registryUrl}}{{registryUrl}}{{else}}https://default.registry.url{{/if}}', + depTypeTemplate: + '{{#if depType}}{{depType}}{{else}}default-dep-type{{/if}}', + }; + const res = await extractPackageFile(json, 'unused', config); + + expect(res).toMatchObject({ + deps: [ + { + depName: 'foo', + packageName: 'fii', + currentValue: '1.2.3', + currentDigest: '1234', + datasource: 'nuget', + versioning: 'maven', + extractVersion: 'custom-extract-version', + registryUrls: ['https://registry.npmjs.org/'], + depType: 'dev', + }, + { + depName: 'default-dep-name', + packageName: 'default-package-name', + currentValue: 'default-current-value', + currentDigest: 'default-current-digest', + datasource: 'default-datasource', + versioning: 'default-versioning', + extractVersion: 'default-extract-version', + registryUrls: ['https://default.registry.url/'], + depType: 'default-dep-type', + }, + ], + }); + }); + + it('logs warning if query result does not match schema', async () => { + const json = codeBlock` + { + "packages": [ + { + "dep_name": "foo", + "package_name": "fii", + "current_value": 1, + "current_digest": "1234", + "data_source": "nuget", + "versioning": "maven", + "extract_version": "custom-extract-version", + "registry_url": "https://registry.npmjs.org", + "dep_type": "dev" + } + ] + }`; + const config = { + fileFormat: 'json', + matchStrings: [ + `packages.{ + "depName": dep_name, + "currentValue": current_value, + "datasource": data_source + }`, + ], + }; + const res = await extractPackageFile(json, 'unused', config); + + expect(res).toBeNull(); + expect(logger.logger.warn).toHaveBeenCalledWith( + expect.anything(), + 'Query results failed schema validation', + ); + }); + + it('returns null if no dependencies found', async () => { + const config = { + fileFormat: 'json', + matchStrings: [ + 'packages.{ "depName": package, "currentValue": version, "versioning ": versioning }', + ], + }; + const res = await extractPackageFile('{}', 'unused', config); + expect(logger.logger.debug).toHaveBeenCalledWith( + { + packageFile: 'unused', + jsonataQuery: + 'packages.{ "depName": package, "currentValue": version, "versioning ": versioning }', + }, + 'The jsonata query returned no matches. Possible error, please check your query. Skipping', + ); + expect(res).toBeNull(); + }); + + it('returns null if invalid template', async () => { + const config = { + fileFormat: 'json', + matchStrings: [ + `{"depName": "foo", "currentValue": "1.0.0", "datasource": "npm"}`, + ], + versioningTemplate: '{{#if versioning}}{{versioning}}{{else}}semver', // invalid template + }; + const res = await extractPackageFile('{}', 'unused', config); + expect(res).toBeNull(); + expect(logger.logger.debug).toHaveBeenCalledWith( + expect.anything(), + 'Error compiling template for JSONata manager', + ); + }); + + it('extracts and does not apply a registryUrlTemplate if the result is an invalid url', async () => { + const config = { + fileFormat: 'json', + matchStrings: [ + `{"depName": "foo", "currentValue": "1.0.0", "datasource": "npm"}`, + ], + registryUrlTemplate: 'this-is-not-a-valid-url-{{depName}}', + }; + const res = await extractPackageFile('{}', 'unused', config); + expect(res).not.toBeNull(); + expect(logger.logger.debug).toHaveBeenCalledWith( + { url: 'this-is-not-a-valid-url-foo' }, + 'Invalid JSONata manager registryUrl', + ); + }); + + it('extracts multiple dependencies with multiple matchStrings', async () => { + const config = { + fileFormat: 'json', + matchStrings: [`{"depName": "foo"}`, `{"depName": "bar"}`], + currentValueTemplate: '1.0.0', + datasourceTemplate: 'npm', + }; + const res = await extractPackageFile('{}', 'unused', config); + expect(res).toMatchObject({ + deps: [ + { + depName: 'foo', + currentValue: '1.0.0', + datasource: 'npm', + }, + { + depName: 'bar', + currentValue: '1.0.0', + datasource: 'npm', + }, + ], + }); + }); +}); diff --git a/lib/modules/manager/custom/jsonata/index.ts b/lib/modules/manager/custom/jsonata/index.ts new file mode 100644 index 00000000000000..8324c0efc7c6bb --- /dev/null +++ b/lib/modules/manager/custom/jsonata/index.ts @@ -0,0 +1,53 @@ +import is from '@sindresorhus/is'; +import type { Category } from '../../../../constants'; +import { logger } from '../../../../logger'; +import { parseJson } from '../../../../util/common'; +import { parseYaml } from '../../../../util/yaml'; +import type { PackageFileContent } from '../../types'; +import type { JsonataExtractConfig } from './types'; +import { handleMatching } from './utils'; + +export const categories: Category[] = ['custom']; + +export const defaultConfig = { + pinDigests: false, +}; +export const supportedDatasources = ['*']; +export const displayName = 'JSONata'; + +export async function extractPackageFile( + content: string, + packageFile: string, + config: JsonataExtractConfig, +): Promise { + let json: unknown; + try { + switch (config.fileFormat) { + case 'json': + json = parseJson(content, packageFile); + break; + case 'yaml': + json = parseYaml(content); + break; + } + } catch (err) { + logger.debug( + { err, fileName: packageFile, fileFormat: config.fileFormat }, + 'Error while parsing file', + ); + return null; + } + + if (is.nullOrUndefined(json)) { + return null; + } + + const deps = await handleMatching(json, packageFile, config); + if (!deps.length) { + return null; + } + + return { + deps, + }; +} diff --git a/lib/modules/manager/custom/jsonata/readme.md b/lib/modules/manager/custom/jsonata/readme.md new file mode 100644 index 00000000000000..257b173b8edb58 --- /dev/null +++ b/lib/modules/manager/custom/jsonata/readme.md @@ -0,0 +1,233 @@ +With `customManagers` using `JSONata` queries you can configure Renovate so it finds dependencies in JSON or YAML files, that are not detected by its other built-in package managers. + +Renovate uses the `jsonata` package to process the `json` or `yaml` file content using the queries. + +For more on the jsonata query language, read the [jsonata query language site](https://docs.jsonata.org/overview.html). + +The JSONata manager is unique in Renovate, because: + +- It can be used with any `datasource` +- It can be configured via [JSONata](https://jsonata.org/) queries +- You can create multiple "JSONata managers" in the same repository + +### Required Fields + +The first two required fields are `fileMatch` and `matchStrings`: + +- `fileMatch` works the same as any manager +- `matchStrings` is a `JSONata` custom manager concept and is used for configuring a jsonata queries + +#### Information that Renovate needs about the dependency + +Before Renovate can look up a dependency and decide about updates, it must have this info about each dependency: + +| Info type | Required | Notes | Docs | +| :--------------------------------------------------- | :------- | :-------------------------------------------------------- | :----------------------------------------------------------------------------- | +| Name of the dependency | Yes | | | +| `datasource` | Yes | Example datasources: npm, Docker, GitHub tags, and so on. | [Supported datasources](../../datasource/index.md#supported-datasources) | +| Version scheme to use. Defaults to `semver-coerced`. | Yes | You may set another version scheme, like `pep440`. | [Supported versioning schemes](../../versioning/index.md#supported-versioning) | + +#### Required fields to be present in the resulting structure returned by the jsonata query + +You must: + +- Capture the `currentValue` of the dependency _or_ use the `currentValueTemplate` template field +- Capture the `depName` or `packageName`. _Or_ use a template field: `depNameTemplate` and `packageNameTemplate` +- Capture the `datasource`, _or_ use the `datasourceTemplate` template field + +#### Optional fields you can include in the resulting structure + +You may use any of these items: + +- `depType`, _or_ use the `depTypeTemplate` template field +- `versioning`, _or_ the use `versioningTemplate` template field. If neither are present, Renovate defaults to `semver-coerced` +- `extractVersion`, _or_ use the `extractVersionTemplate` template field +- `currentDigest` +- `registryUrl`, _or_ use the `registryUrlTemplate` template field. If it's a valid URL, it will be converted to the `registryUrls` field as a single-length array +- `indentation`. Must be empty, _or_ whitespace. Else Renovate restes only `indentation` to an empty string + +### Usage + +When you configure a JSONata manager, use the following syntax: + +```javascript +{ + "customManagers": [ + { + "customType": "jsonata", + "fileFormat": "json", + "fileMatch": [""], + "matchStrings": [''], + ... + } + ] +} +``` + +Overwrite the `` placeholder text with your [JSONata](https://docs.jsonata.org/overview.html) query. +The JSONata query transforms the content to a JSON object, similar to the this: + +```javascript dependencies information extracted usig jsonata query +[ + { + depName: 'some_dep', + currentValue: '1.0.0', + datasource: 'docker', + versioning: 'semver', + }, +]; +``` + +Creating your Renovate JSONata manager config is easier if you understand JSONata queries. +We recommend you follow these steps: + +1. Read the official JSONata query language docs +2. Check our example queries below +3. You're ready to make your own config + +Alternatively you can "try and error" to a working config, by adjusting our examples. + +YAML files are parsed as multi document files. + +#### Example queries + +Below are some example queries for the generic JSON manager. +You can also use the [JSONata test website](https://try.jsonata.org) to experiment with queries. + +```json title="Dependencies spread in different nodes, and we want to limit the extraction to a particular node" +{ + "production": [ + { + "version": "1.2.3", + "package": "foo" + } + ], + "development": [ + { + "version": "4.5.6", + "package": "bar" + } + ] +} +``` + +Query: + +``` +production.{ "depName": package, "currentValue": version } +``` + +```json title="Dependencies spread in different nodes, and we want to extract all of them as if they were in the same node" +{ + "production": [ + { + "version": "1.2.3", + "package": "foo" + } + ], + "development": [ + { + "version": "4.5.6", + "package": "bar" + } + ] +} +``` + +Query: + +``` +*.{ "depName": package, "currentValue": version } +``` + +```json title="The dependency name is in a JSON node name, and the version is in a child leaf to that node" +{ + "foo": { + "version": "1.2.3" + }, + "bar": { + "version": "4.5.6" + } +} +``` + +Query: + +``` +$each(function($v, $n) { { "depName": $n, "currentValue": $v.version } }) +``` + +```json title="The dependency name and its version are both value nodes of the same parent node" +{ + "packages": [ + { + "version": "1.2.3", + "package": "foo" + }, + { + "version": "4.5.6", + "package": "bar" + } + ] +} +``` + +Query: + +``` +packages.{ "depName": package, "currentValue": version } +``` + +```json title="The dependency name and version are part of the same string" +{ + "packages": ["foo@1.2.3", "bar@4.5.6"] +} +``` + +Query: + +``` +$map($map(packages, function ($v) { $split($v, "@") }), function ($v) { { "depName": $v[0], "currentVersion": $v[1] } }) +``` + +```json title="JSONata manager config to extract deps from a package.json file in the Renovate repository" +{ + "customType": "jsonata", + "fileMatch": ["package.json"], + "matchStrings": [ + "$each(dependencies, function($v, $k) { {\"depName\":$k, \"currentValue\": $v, \"depType\": \"dependencies\"}})", + "$each(devDependencies, function($v, $k) { {\"depName\":$k, \"currentValue\": $v, \"depType\": \"devDependencies\"}})", + "$each(optionalDependencies, function($v, $k) { {\"depName\":$k, \"currentValue\": $v, \"depType\": \"optionalDependencies\"}})", + "{ \"depName\": \"pnpm\", \"currentValue\": $substring(packageManager, 5), \"depType\": \"packageManager\"}" + ], + "datasourceTemplate": "npm" +} +``` + +```yaml title="Dependencies in a single node, and we want to extract all of them" +packages: + - version: 1.2.3 + package: foo +``` + +Query: + +``` +packages.{ "depName": package, "currentValue": version } +``` + +```yaml title="Dependencies in a single node in a multi document yaml, and we want to extract all of them" +packages: + - version: 1.2.3 + package: foo +--- +packages: + - version: 1.2.5 + package: bar +``` + +Query: + +``` +packages.{ "depName": package, "currentValue": version } +``` diff --git a/lib/modules/manager/custom/jsonata/schema.ts b/lib/modules/manager/custom/jsonata/schema.ts new file mode 100644 index 00000000000000..cf363c576b6c6c --- /dev/null +++ b/lib/modules/manager/custom/jsonata/schema.ts @@ -0,0 +1,20 @@ +import { z } from 'zod'; + +const DepObjectSchema = z.object({ + currentValue: z.string().optional(), + datasource: z.string().optional(), + depName: z.string().optional(), + packageName: z.string().optional(), + currentDigest: z.string().optional(), + versioning: z.string().optional(), + depType: z.string().optional(), + registryUrl: z.string().optional(), + extractVersion: z.string().optional(), + indentation: z.string().optional(), +}); + +export const QueryResultZodSchema = z + .union([z.array(DepObjectSchema), DepObjectSchema]) + .transform((input) => { + return Array.isArray(input) ? input : [input]; + }); diff --git a/lib/modules/manager/custom/jsonata/types.ts b/lib/modules/manager/custom/jsonata/types.ts new file mode 100644 index 00000000000000..70852a81bfc41d --- /dev/null +++ b/lib/modules/manager/custom/jsonata/types.ts @@ -0,0 +1,25 @@ +import type { ExtractConfig } from '../../types'; + +export interface JSONataManagerTemplates { + depNameTemplate?: string; + packageNameTemplate?: string; + datasourceTemplate?: string; + versioningTemplate?: string; + depTypeTemplate?: string; + currentValueTemplate?: string; + currentDigestTemplate?: string; + extractVersionTemplate?: string; + registryUrlTemplate?: string; +} + +export interface JSONataManagerConfig extends JSONataManagerTemplates { + fileFormat: string; + matchStrings: string[]; +} + +export interface JsonataExtractConfig + extends ExtractConfig, + JSONataManagerTemplates { + fileFormat: string; + matchStrings: string[]; +} diff --git a/lib/modules/manager/custom/jsonata/utils.ts b/lib/modules/manager/custom/jsonata/utils.ts new file mode 100644 index 00000000000000..f762dfbdbebeaa --- /dev/null +++ b/lib/modules/manager/custom/jsonata/utils.ts @@ -0,0 +1,110 @@ +import is from '@sindresorhus/is'; +import jsonata from 'jsonata'; +import { migrateDatasource } from '../../../../config/migrations/custom/datasource-migration'; +import { logger } from '../../../../logger'; +import * as template from '../../../../util/template'; +import { parseUrl } from '../../../../util/url'; +import type { PackageDependency } from '../../types'; +import type { ValidMatchFields } from '../utils'; +import { checkIsValidDependency, validMatchFields } from '../utils'; +import { QueryResultZodSchema } from './schema'; +import type { JSONataManagerTemplates, JsonataExtractConfig } from './types'; + +export async function handleMatching( + json: unknown, + packageFile: string, + config: JsonataExtractConfig, +): Promise { + let results: Record[] = []; + const { matchStrings: jsonataQueries } = config; + for (const query of jsonataQueries) { + // won't fail as this is verified during config validation + const jsonataExpression = jsonata(query); + // this does not throw error, just returns undefined if no matches + const queryResult = await jsonataExpression.evaluate(json); + + // allows empty dep object cause templates can be used to configure the required fields + // if some issues arise then the isValidDependency call will catch them later on + if (!queryResult || is.emptyArray(queryResult)) { + logger.debug( + { + jsonataQuery: query, + packageFile, + }, + 'The jsonata query returned no matches. Possible error, please check your query. Skipping', + ); + return []; + } + + const parsed = QueryResultZodSchema.safeParse(queryResult); + if (parsed.success) { + results = results.concat(parsed.data); + } else { + logger.warn( + { err: parsed.error, jsonataQuery: query, packageFile, queryResult }, + 'Query results failed schema validation', + ); + return []; + } + } + + return results + .map((dep) => createDependency(dep, config)) + .filter(is.truthy) + .filter((dep) => + checkIsValidDependency(dep, packageFile, 'custom.jsonata'), + ); +} + +export function createDependency( + queryResult: Record, + config: JsonataExtractConfig, +): PackageDependency | null { + const dependency: PackageDependency = {}; + + for (const field of validMatchFields) { + const fieldTemplate = `${field}Template` as keyof JSONataManagerTemplates; + const tmpl = config[fieldTemplate]; + if (tmpl) { + try { + const compiled = template.compile(tmpl, queryResult, false); + updateDependency(field, compiled, dependency); + } catch { + logger.debug( + { template: tmpl }, + 'Error compiling template for JSONata manager', + ); + return null; + } + } else if (queryResult[field]) { + updateDependency(field, queryResult[field], dependency); + } + } + return dependency; +} + +function updateDependency( + field: ValidMatchFields, + value: string, + dependency: PackageDependency, +): PackageDependency { + switch (field) { + case 'registryUrl': { + const url = parseUrl(value)?.toString(); + if (!url) { + logger.debug({ url: value }, 'Invalid JSONata manager registryUrl'); + break; + } + dependency.registryUrls = [url]; + break; + } + case 'datasource': + dependency.datasource = migrateDatasource(value); + break; + default: + dependency[field] = value; + break; + } + + return dependency; +} diff --git a/lib/modules/manager/custom/types.ts b/lib/modules/manager/custom/types.ts index de387685f80717..a6aa1ff22ed825 100644 --- a/lib/modules/manager/custom/types.ts +++ b/lib/modules/manager/custom/types.ts @@ -1,10 +1,15 @@ +import type { JSONataManagerConfig } from './jsonata/types'; import type { RegexManagerConfig } from './regex/types'; -export interface CustomExtractConfig extends Partial {} +export interface CustomExtractConfig + extends Partial, + Partial {} -export type CustomManagerName = 'regex'; +export type CustomManagerName = 'jsonata' | 'regex'; -export interface CustomManager extends Partial { +export interface CustomManager + extends Partial, + Partial { customType: CustomManagerName; fileMatch: string[]; } diff --git a/lib/modules/manager/devbox/artifacts.spec.ts b/lib/modules/manager/devbox/artifacts.spec.ts new file mode 100644 index 00000000000000..f2ffbd4dcfc2de --- /dev/null +++ b/lib/modules/manager/devbox/artifacts.spec.ts @@ -0,0 +1,243 @@ +import { codeBlock } from 'common-tags'; +import { mockExecAll } from '../../../../test/exec-util'; +import { fs, git, partial } from '../../../../test/util'; +import { GlobalConfig } from '../../../config/global'; +import type { RepoGlobalConfig } from '../../../config/types'; +import type { StatusResult } from '../../../util/git/types'; +import type { UpdateArtifact } from '../types'; +import { updateArtifacts } from './artifacts'; + +jest.mock('../../../util/exec/env'); +jest.mock('../../../util/git'); +jest.mock('../../../util/fs'); + +const globalConfig: RepoGlobalConfig = { + localDir: '', +}; + +const devboxJson = codeBlock` + { + "$schema": "https://raw.githubusercontent.com/jetpack-io/devbox/0.10.1/.schema/devbox.schema.json", + "packages": ["nodejs@20", "metabase@0.49.1", "postgresql@latest", "gh@latest"], + } +`; + +describe('modules/manager/devbox/artifacts', () => { + describe('updateArtifacts()', () => { + let updateArtifact: UpdateArtifact; + + beforeEach(() => { + GlobalConfig.set(globalConfig); + updateArtifact = { + config: {}, + newPackageFileContent: '', + packageFileName: '', + updatedDeps: [], + }; + }); + + it('skips if no updatedDeps and no lockFileMaintenance', async () => { + expect(await updateArtifacts(updateArtifact)).toBeNull(); + }); + + it('skips if no lock file in config', async () => { + updateArtifact.updatedDeps = [{}]; + expect(await updateArtifacts(updateArtifact)).toBeNull(); + }); + + it('skips if cannot read lock file', async () => { + updateArtifact.updatedDeps = [ + { manager: 'devbox', lockFiles: ['devbox.lock'] }, + ]; + expect(await updateArtifacts(updateArtifact)).toBeNull(); + }); + + it('returns installed devbox.lock', async () => { + fs.getSiblingFileName.mockReturnValueOnce('devbox.lock'); + fs.readLocalFile.mockResolvedValueOnce(codeBlock`{}`); + const execSnapshots = mockExecAll(); + const oldLockFileContent = Buffer.from('Old devbox.lock'); + const newLockFileContent = Buffer.from('New devbox.lock'); + fs.readLocalFile.mockResolvedValueOnce(oldLockFileContent as never); + fs.readLocalFile.mockResolvedValueOnce(newLockFileContent as never); + expect( + await updateArtifacts({ + packageFileName: 'devbox.json', + newPackageFileContent: devboxJson, + updatedDeps: [{ manager: 'devbox', lockFiles: ['devbox.lock'] }], + config: {}, + }), + ).toEqual([ + { + file: { + type: 'addition', + path: 'devbox.lock', + contents: newLockFileContent, + }, + }, + ]); + expect(execSnapshots).toMatchObject([ + { + cmd: 'devbox install', + options: { + cwd: '.', + encoding: 'utf-8', + env: {}, + maxBuffer: 10485760, + timeout: 900000, + }, + }, + ]); + }); + + it('returns updated devbox.lock', async () => { + fs.getSiblingFileName.mockReturnValueOnce('devbox.lock'); + fs.readLocalFile.mockResolvedValueOnce(codeBlock`{}`); + const execSnapshots = mockExecAll(); + git.getRepoStatus.mockResolvedValueOnce( + partial({ + modified: ['devbox.lock'], + }), + ); + const oldLockFileContent = Buffer.from('old devbox.lock'); + const newLockFileContent = Buffer.from('New devbox.lock'); + fs.readLocalFile.mockResolvedValueOnce(oldLockFileContent as never); + fs.readLocalFile.mockResolvedValueOnce(newLockFileContent as never); + expect( + await updateArtifacts({ + packageFileName: 'devbox.json', + newPackageFileContent: devboxJson, + updatedDeps: [{}], + config: { + isLockFileMaintenance: true, + }, + }), + ).toEqual([ + { + file: { + type: 'addition', + path: 'devbox.lock', + contents: newLockFileContent, + }, + }, + ]); + expect(execSnapshots).toMatchObject([ + { + cmd: 'devbox update', + options: { + cwd: '.', + encoding: 'utf-8', + env: {}, + maxBuffer: 10485760, + timeout: 900000, + }, + }, + ]); + }); + + it('returns null if no changes are found', async () => { + fs.getSiblingFileName.mockReturnValueOnce('devbox.lock'); + fs.readLocalFile.mockResolvedValueOnce(codeBlock`{}`); + git.getRepoStatus.mockResolvedValueOnce( + partial({ + modified: [], + }), + ); + mockExecAll(); + expect( + await updateArtifacts({ + packageFileName: 'devbox.json', + newPackageFileContent: devboxJson, + updatedDeps: [], + config: {}, + }), + ).toBeNull(); + }); + + it('returns null if devbox.lock not found after update', async () => { + fs.getSiblingFileName.mockReturnValueOnce('devbox.lock'); + fs.readLocalFile.mockResolvedValueOnce(codeBlock`{}`); + git.getRepoStatus.mockResolvedValueOnce( + partial({ + modified: [], + }), + ); + mockExecAll(); + const oldLockFileContent = Buffer.from('Old devbox.lock'); + fs.readLocalFile.mockResolvedValueOnce(oldLockFileContent as never); + expect( + await updateArtifacts({ + packageFileName: 'devbox.json', + newPackageFileContent: devboxJson, + updatedDeps: [{}], + config: {}, + }), + ).toBeNull(); + }); + + it('returns null if devbox.lock not found', async () => { + fs.getSiblingFileName.mockReturnValueOnce('devbox.lock'); + fs.readLocalFile.mockResolvedValueOnce(codeBlock`{}`); + git.getRepoStatus.mockResolvedValueOnce( + partial({ + modified: [], + }), + ); + mockExecAll(); + fs.readLocalFile.mockResolvedValueOnce(null); + expect( + await updateArtifacts({ + packageFileName: 'devbox.json', + newPackageFileContent: devboxJson, + updatedDeps: [{}], + config: {}, + }), + ).toBeNull(); + }); + + it('returns null if no lock file changes are found', async () => { + fs.getSiblingFileName.mockReturnValueOnce('devbox.lock'); + fs.readLocalFile.mockResolvedValueOnce(codeBlock`{}`); + git.getRepoStatus.mockResolvedValueOnce( + partial({ + modified: [], + }), + ); + mockExecAll(); + const oldLockFileContent = Buffer.from('Old devbox.lock'); + fs.readLocalFile.mockResolvedValueOnce(oldLockFileContent as never); + fs.readLocalFile.mockResolvedValueOnce(oldLockFileContent as never); + expect( + await updateArtifacts({ + packageFileName: 'devbox.json', + newPackageFileContent: devboxJson, + updatedDeps: [{}], + config: {}, + }), + ).toBeNull(); + }); + + it('returns an artifact error on failure', async () => { + fs.getSiblingFileName.mockReturnValueOnce('devbox.lock'); + const newLockFileContent = codeBlock`{}`; + const oldLockFileContent = Buffer.from('New devbox.lock'); + fs.readLocalFile.mockResolvedValueOnce(oldLockFileContent as never); + fs.readLocalFile.mockResolvedValueOnce(newLockFileContent as never); + expect( + await updateArtifacts({ + packageFileName: 'devbox.json', + newPackageFileContent: devboxJson, + updatedDeps: [{}], + config: {}, + }), + ).toEqual([ + { + artifactError: { + lockFile: 'devbox.lock', + stderr: "Cannot read properties of undefined (reading 'stdout')", + }, + }, + ]); + }); + }); +}); diff --git a/lib/modules/manager/devbox/artifacts.ts b/lib/modules/manager/devbox/artifacts.ts new file mode 100644 index 00000000000000..c90c6faa3345e6 --- /dev/null +++ b/lib/modules/manager/devbox/artifacts.ts @@ -0,0 +1,82 @@ +import is from '@sindresorhus/is'; +import { logger } from '../../../logger'; +import { exec } from '../../../util/exec'; +import type { ExecOptions } from '../../../util/exec/types'; +import { getSiblingFileName, readLocalFile } from '../../../util/fs'; +import type { UpdateArtifact, UpdateArtifactsResult } from '../types'; + +export async function updateArtifacts( + updateConfig: UpdateArtifact, +): Promise { + const lockFileName = getSiblingFileName( + updateConfig.packageFileName, + 'devbox.lock', + ); + const existingLockFileContent = await readLocalFile(lockFileName, 'utf8'); + if (!existingLockFileContent) { + logger.debug('No devbox.lock found'); + return null; + } + const execOptions: ExecOptions = { + cwdFile: updateConfig.packageFileName, + toolConstraints: [ + { + toolName: 'devbox', + constraint: updateConfig.config.constraints?.devbox, + }, + ], + docker: {}, + userConfiguredEnv: updateConfig.config.env, + }; + + let cmd = ''; + if ( + updateConfig.config.isLockFileMaintenance || + updateConfig.config.updateType === 'lockFileMaintenance' + ) { + cmd += 'devbox update'; + } else if (is.nonEmptyArray(updateConfig.updatedDeps)) { + cmd += 'devbox install'; + } else { + logger.trace('No updated devbox packages - returning null'); + return null; + } + + const oldLockFileContent = await readLocalFile(lockFileName); + if (!oldLockFileContent) { + logger.trace(`No ${lockFileName} found`); + return null; + } + + try { + await exec(cmd, execOptions); + const newLockFileContent = await readLocalFile(lockFileName); + + if ( + !newLockFileContent || + Buffer.compare(oldLockFileContent, newLockFileContent) === 0 + ) { + return null; + } + logger.trace('Returning updated devbox.lock'); + return [ + { + file: { + type: 'addition', + path: lockFileName, + contents: newLockFileContent, + }, + }, + ]; + } catch (err) { + logger.warn({ err }, 'Error updating devbox.lock'); + return [ + { + artifactError: { + lockFile: lockFileName, + stderr: err.message, + }, + }, + ]; + } +} diff --git a/lib/modules/manager/devbox/extract.spec.ts b/lib/modules/manager/devbox/extract.spec.ts new file mode 100644 index 00000000000000..ba295b3a5ca43c --- /dev/null +++ b/lib/modules/manager/devbox/extract.spec.ts @@ -0,0 +1,291 @@ +import { codeBlock } from 'common-tags'; +import { extractPackageFile } from '.'; + +describe('modules/manager/devbox/extract', () => { + describe('extractPackageFile', () => { + it('returns null when the devbox JSON file is empty', () => { + const result = extractPackageFile('', 'devbox.lock'); + expect(result).toBeNull(); + }); + + it('returns null when the devbox JSON file is malformed', () => { + const result = extractPackageFile('malformed json}}}}}', 'devbox.lock'); + expect(result).toBeNull(); + }); + + it('returns null when the devbox JSON file has no packages', () => { + const result = extractPackageFile('{}', 'devbox.lock'); + expect(result).toBeNull(); + }); + + it('returns a package dependency when the devbox JSON file has a single package', () => { + const result = extractPackageFile( + codeBlock` + { + "packages": ["nodejs@20.1.8"] + } + `, + 'devbox.lock', + ); + expect(result).toEqual({ + deps: [ + { + depName: 'nodejs', + currentValue: '20.1.8', + datasource: 'devbox', + }, + ], + }); + }); + + it('returns a package dependency when the devbox JSON file has a single package with a version object', () => { + const result = extractPackageFile( + codeBlock` + { + "packages": { + "nodejs": "20.1.8" + } + } + `, + 'devbox.lock', + ); + expect(result).toEqual({ + deps: [ + { + depName: 'nodejs', + currentValue: '20.1.8', + datasource: 'devbox', + }, + ], + }); + }); + + it('returns invalid-version when the devbox JSON file has a single package with an invalid version', () => { + const result = extractPackageFile( + codeBlock` + { + "packages": { + "nodejs": "^20.1.8" + } + } + `, + 'devbox.lock', + ); + expect(result).toEqual({ + deps: [ + { + currentValue: '^20.1.8', + datasource: 'devbox', + depName: 'nodejs', + skipReason: 'invalid-version', + }, + ], + }); + }); + + it('returns a package dependency when the devbox JSON file has multiple packages', () => { + const result = extractPackageFile( + codeBlock` + { + "packages": ["nodejs@20.1.8", "yarn@1.22.10"] + } + `, + 'devbox.lock', + ); + expect(result).toEqual({ + deps: [ + { + depName: 'nodejs', + currentValue: '20.1.8', + datasource: 'devbox', + }, + { + depName: 'yarn', + currentValue: '1.22.10', + datasource: 'devbox', + }, + ], + }); + }); + + it('returns a package dependency when the devbox JSON file has multiple packages with in a packages object', () => { + const result = extractPackageFile( + codeBlock` + { + "packages": { + "nodejs": "20.1.8", + "yarn": "1.22.10" + } + } + `, + 'devbox.lock', + ); + expect(result).toEqual({ + deps: [ + { + depName: 'nodejs', + currentValue: '20.1.8', + datasource: 'devbox', + }, + { + depName: 'yarn', + currentValue: '1.22.10', + datasource: 'devbox', + }, + ], + }); + }); + + it('returns a package dependency when the devbox JSON file has multiple packages with package objects', () => { + const result = extractPackageFile( + codeBlock` + { + "packages": { + "nodejs": { + "version": "20.1.8" + }, + "yarn": { + "version": "1.22.10" + } + } + } + `, + 'devbox.lock', + ); + expect(result).toEqual({ + deps: [ + { + depName: 'nodejs', + currentValue: '20.1.8', + datasource: 'devbox', + }, + { + depName: 'yarn', + currentValue: '1.22.10', + datasource: 'devbox', + }, + ], + }); + }); + + it('returns invalid dependencies', () => { + const result = extractPackageFile( + codeBlock` + { + "packages": { + "nodejs": "20.1.8", + "yarn": "1.22.10", + "invalid": "invalid" + } + } + `, + 'devbox.lock', + ); + expect(result).toEqual({ + deps: [ + { + depName: 'nodejs', + currentValue: '20.1.8', + datasource: 'devbox', + }, + { + depName: 'yarn', + currentValue: '1.22.10', + datasource: 'devbox', + }, + { + currentValue: 'invalid', + datasource: 'devbox', + depName: 'invalid', + skipReason: 'invalid-version', + }, + ], + }); + }); + + it('returns invalid dependencies with package objects', () => { + const result = extractPackageFile( + codeBlock` + { + "packages": { + "nodejs": "20.1.8", + "yarn": "1.22.10", + "invalid": { + "version": "invalid" + } + } + } + `, + 'devbox.lock', + ); + expect(result).toEqual({ + deps: [ + { + depName: 'nodejs', + currentValue: '20.1.8', + datasource: 'devbox', + }, + { + depName: 'yarn', + currentValue: '1.22.10', + datasource: 'devbox', + }, + { + currentValue: 'invalid', + datasource: 'devbox', + depName: 'invalid', + skipReason: 'invalid-version', + }, + ], + }); + }); + + it('returns invalid dependencies from the packages array', () => { + const result = extractPackageFile( + codeBlock` + { + "packages": ["nodejs@20.1.8", "yarn@1.22.10", "invalid@invalid", "invalid2"] + } + `, + 'devbox.lock', + ); + expect(result).toEqual({ + deps: [ + { + depName: 'nodejs', + currentValue: '20.1.8', + datasource: 'devbox', + }, + { + depName: 'yarn', + currentValue: '1.22.10', + datasource: 'devbox', + }, + { + currentValue: 'invalid', + datasource: 'devbox', + depName: 'invalid', + skipReason: 'invalid-version', + }, + { + datasource: 'devbox', + depName: 'invalid2', + skipReason: 'not-a-version', + }, + ], + }); + }); + + it('returns null if there are no dependencies', () => { + const result = extractPackageFile( + codeBlock` + { + "packages": [] + } + `, + 'devbox.lock', + ); + expect(result).toBeNull(); + }); + }); +}); diff --git a/lib/modules/manager/devbox/extract.ts b/lib/modules/manager/devbox/extract.ts new file mode 100644 index 00000000000000..1703779ea89ed4 --- /dev/null +++ b/lib/modules/manager/devbox/extract.ts @@ -0,0 +1,17 @@ +import { logger, withMeta } from '../../../logger'; +import type { PackageFileContent } from '../types'; +import { DevboxSchema } from './schema'; + +export function extractPackageFile( + content: string, + packageFile: string, +): PackageFileContent | null { + logger.trace('devbox.extractPackageFile()'); + + const deps = withMeta({ packageFile }, () => DevboxSchema.parse(content)); + if (!deps.length) { + return null; + } + + return { deps }; +} diff --git a/lib/modules/manager/devbox/index.ts b/lib/modules/manager/devbox/index.ts new file mode 100644 index 00000000000000..d6764af34e43ef --- /dev/null +++ b/lib/modules/manager/devbox/index.ts @@ -0,0 +1,12 @@ +import { DevboxDatasource } from '../../datasource/devbox'; + +export { extractPackageFile } from './extract'; +export { updateArtifacts } from './artifacts'; + +export const supportsLockFileMaintenance = true; + +export const defaultConfig = { + fileMatch: ['(^|/)devbox\\.json$'], +}; + +export const supportedDatasources = [DevboxDatasource.id]; diff --git a/lib/modules/manager/devbox/readme.md b/lib/modules/manager/devbox/readme.md new file mode 100644 index 00000000000000..5b91520f9fed8b --- /dev/null +++ b/lib/modules/manager/devbox/readme.md @@ -0,0 +1,5 @@ +Used for updating [devbox](https://www.jetify.com/devbox) projects. + +Devbox is a tool for creating isolated, reproducible development environments that run anywhere. + +It uses nix packages sourced from the devbox package registry. diff --git a/lib/modules/manager/devbox/schema.ts b/lib/modules/manager/devbox/schema.ts new file mode 100644 index 00000000000000..77f88ee8f18dc5 --- /dev/null +++ b/lib/modules/manager/devbox/schema.ts @@ -0,0 +1,63 @@ +import { z } from 'zod'; +import { logger } from '../../../logger'; +import { + Jsonc, + LooseArray, + LooseRecord, + withDebugMessage, +} from '../../../util/schema-utils'; +import { DevboxDatasource } from '../../datasource/devbox'; +import { api as devboxVersioning } from '../../versioning/devbox'; +import type { PackageDependency } from '../types'; + +const DevboxEntry = z + .array(z.string()) + .min(1) + .transform(([depName, currentValue]) => { + const dep: PackageDependency = { + datasource: DevboxDatasource.id, + depName, + }; + + if (!currentValue) { + logger.trace( + { depName }, + 'Devbox: skipping invalid devbox dependency in devbox JSON file.', + ); + dep.skipReason = 'not-a-version'; + return dep; + } + + dep.currentValue = currentValue; + + if (!devboxVersioning.isValid(currentValue)) { + logger.debug( + { depName }, + 'Devbox: skipping invalid devbox dependency in devbox JSON file.', + ); + dep.skipReason = 'invalid-version'; + return dep; + } + + return dep; + }); + +export const DevboxSchema = Jsonc.pipe( + z.object({ + packages: z + .union([ + LooseArray(z.string().transform((pkg) => pkg.split('@'))), + LooseRecord( + z.union([ + z.string(), + z + .object({ version: z.string() }) + .transform(({ version }) => version), + ]), + ).transform((obj) => Object.entries(obj)), + ]) + .pipe(LooseArray(DevboxEntry)), + }), +) + .transform(({ packages }) => packages) + .catch(withDebugMessage([], 'Devbox: error parsing file')); diff --git a/lib/modules/manager/devcontainer/extract.spec.ts b/lib/modules/manager/devcontainer/extract.spec.ts index 76b5b28993f898..0a6a3fdd5d3b49 100644 --- a/lib/modules/manager/devcontainer/extract.spec.ts +++ b/lib/modules/manager/devcontainer/extract.spec.ts @@ -33,7 +33,7 @@ describe('modules/manager/devcontainer/extract', () => { { // hello "features": { - "devcontainer.registry.renovate.com/test/features/first:1.2.3": {} + "devcontainer.registry.renovate.com/test/features/first:1.2.3": {}, } }`); const extractConfig = {}; @@ -54,6 +54,8 @@ describe('modules/manager/devcontainer/extract', () => { currentValue: '1.2.3', datasource: 'docker', depName: 'devcontainer.registry.renovate.com/test/features/first', + packageName: + 'devcontainer.registry.renovate.com/test/features/first', depType: 'feature', pinDigests: false, replaceString: @@ -90,6 +92,8 @@ describe('modules/manager/devcontainer/extract', () => { currentValue: '1.2.3', datasource: 'docker', depName: 'devcontainer.registry.renovate.com/test/features/first', + packageName: + 'devcontainer.registry.renovate.com/test/features/first', depType: 'feature', pinDigests: false, replaceString: @@ -102,6 +106,8 @@ describe('modules/manager/devcontainer/extract', () => { currentValue: '4.5.6', datasource: 'docker', depName: 'devcontainer.registry.renovate.com/test/features/second', + packageName: + 'devcontainer.registry.renovate.com/test/features/second', depType: 'feature', pinDigests: false, replaceString: @@ -139,6 +145,7 @@ describe('modules/manager/devcontainer/extract', () => { currentValue: '1.2.3', datasource: 'docker', depName: 'devcontainer.registry.renovate.com/test/image', + packageName: 'devcontainer.registry.renovate.com/test/image', depType: 'image', replaceString: 'devcontainer.registry.renovate.com/test/image:1.2.3', @@ -150,6 +157,7 @@ describe('modules/manager/devcontainer/extract', () => { currentValue: '4.5.6', datasource: 'docker', depName: 'devcontainer.registry.renovate.com/test/feature', + packageName: 'devcontainer.registry.renovate.com/test/feature', depType: 'feature', pinDigests: false, replaceString: @@ -183,6 +191,7 @@ describe('modules/manager/devcontainer/extract', () => { currentValue: '1.2.3', datasource: 'docker', depName: 'devcontainer.registry.renovate.com/test/image', + packageName: 'devcontainer.registry.renovate.com/test/image', depType: 'image', replaceString: 'devcontainer.registry.renovate.com/test/image:1.2.3', @@ -346,6 +355,7 @@ describe('modules/manager/devcontainer/extract', () => { currentValue: '1.2.3', datasource: 'docker', depName: 'devcontainer.registry.renovate.com/test/feature', + packageName: 'devcontainer.registry.renovate.com/test/feature', depType: 'feature', pinDigests: false, replaceString: diff --git a/lib/modules/manager/docker-compose/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/docker-compose/__snapshots__/extract.spec.ts.snap index 37f2584f537f37..d977673c61ec12 100644 --- a/lib/modules/manager/docker-compose/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/docker-compose/__snapshots__/extract.spec.ts.snap @@ -8,6 +8,7 @@ exports[`modules/manager/docker-compose/extract extractPackageFile() extracts mu "currentValue": "alpine", "datasource": "docker", "depName": "quay.io/something/redis", + "packageName": "quay.io/something/redis", "replaceString": "quay.io/something/redis:alpine", }, { @@ -16,6 +17,7 @@ exports[`modules/manager/docker-compose/extract extractPackageFile() extracts mu "currentValue": "10.0.0", "datasource": "docker", "depName": "node", + "packageName": "node", "replaceString": "node:10.0.0", }, { @@ -24,6 +26,7 @@ exports[`modules/manager/docker-compose/extract extractPackageFile() extracts mu "currentValue": "9.4.0", "datasource": "docker", "depName": "postgres", + "packageName": "postgres", "replaceString": "postgres:9.4.0", }, { @@ -32,6 +35,7 @@ exports[`modules/manager/docker-compose/extract extractPackageFile() extracts mu "currentValue": "before", "datasource": "docker", "depName": "dockersamples/examplevotingapp_vote", + "packageName": "dockersamples/examplevotingapp_vote", "replaceString": "dockersamples/examplevotingapp_vote:before", }, { @@ -40,6 +44,7 @@ exports[`modules/manager/docker-compose/extract extractPackageFile() extracts mu "currentValue": "before", "datasource": "docker", "depName": "dockersamples/examplevotingapp_result", + "packageName": "dockersamples/examplevotingapp_result", "replaceString": "dockersamples/examplevotingapp_result:before", }, { @@ -48,6 +53,7 @@ exports[`modules/manager/docker-compose/extract extractPackageFile() extracts mu "currentValue": undefined, "datasource": "docker", "depName": "dockersamples/examplevotingapp_worker", + "packageName": "dockersamples/examplevotingapp_worker", "replaceString": "dockersamples/examplevotingapp_worker", }, { @@ -56,6 +62,7 @@ exports[`modules/manager/docker-compose/extract extractPackageFile() extracts mu "currentValue": "stable", "datasource": "docker", "depName": "dockersamples/visualizer", + "packageName": "dockersamples/visualizer", "replaceString": "dockersamples/visualizer:stable", }, { @@ -75,6 +82,7 @@ exports[`modules/manager/docker-compose/extract extractPackageFile() extracts mu "currentValue": "alpine", "datasource": "docker", "depName": "quay.io/something/redis", + "packageName": "quay.io/something/redis", "replaceString": "quay.io/something/redis:alpine", }, { @@ -83,6 +91,7 @@ exports[`modules/manager/docker-compose/extract extractPackageFile() extracts mu "currentValue": "10.0.0", "datasource": "docker", "depName": "node", + "packageName": "node", "replaceString": "node:10.0.0", }, { @@ -91,6 +100,7 @@ exports[`modules/manager/docker-compose/extract extractPackageFile() extracts mu "currentValue": "9.4.0", "datasource": "docker", "depName": "postgres", + "packageName": "postgres", "replaceString": "postgres:9.4.0", }, { @@ -99,6 +109,7 @@ exports[`modules/manager/docker-compose/extract extractPackageFile() extracts mu "currentValue": "before", "datasource": "docker", "depName": "dockersamples/examplevotingapp_vote", + "packageName": "dockersamples/examplevotingapp_vote", "replaceString": "dockersamples/examplevotingapp_vote:before", }, { @@ -107,6 +118,7 @@ exports[`modules/manager/docker-compose/extract extractPackageFile() extracts mu "currentValue": "before", "datasource": "docker", "depName": "dockersamples/examplevotingapp_result", + "packageName": "dockersamples/examplevotingapp_result", "replaceString": "dockersamples/examplevotingapp_result:before", }, { @@ -115,6 +127,7 @@ exports[`modules/manager/docker-compose/extract extractPackageFile() extracts mu "currentValue": undefined, "datasource": "docker", "depName": "dockersamples/examplevotingapp_worker", + "packageName": "dockersamples/examplevotingapp_worker", "replaceString": "dockersamples/examplevotingapp_worker", }, { @@ -123,6 +136,7 @@ exports[`modules/manager/docker-compose/extract extractPackageFile() extracts mu "currentValue": "stable", "datasource": "docker", "depName": "dockersamples/visualizer", + "packageName": "dockersamples/visualizer", "replaceString": "dockersamples/visualizer:stable", }, { @@ -142,6 +156,7 @@ exports[`modules/manager/docker-compose/extract extractPackageFile() extracts mu "currentValue": "alpine", "datasource": "docker", "depName": "quay.io/something/redis", + "packageName": "quay.io/something/redis", "replaceString": "quay.io/something/redis:alpine", }, { @@ -150,6 +165,7 @@ exports[`modules/manager/docker-compose/extract extractPackageFile() extracts mu "currentValue": "10.0.0", "datasource": "docker", "depName": "node", + "packageName": "node", "replaceString": "node:10.0.0", }, { @@ -158,6 +174,7 @@ exports[`modules/manager/docker-compose/extract extractPackageFile() extracts mu "currentValue": "9.4.0", "datasource": "docker", "depName": "postgres", + "packageName": "postgres", "replaceString": "postgres:9.4.0", }, { @@ -166,6 +183,7 @@ exports[`modules/manager/docker-compose/extract extractPackageFile() extracts mu "currentValue": "before", "datasource": "docker", "depName": "dockersamples/examplevotingapp_vote", + "packageName": "dockersamples/examplevotingapp_vote", "replaceString": "dockersamples/examplevotingapp_vote:before", }, { @@ -174,6 +192,7 @@ exports[`modules/manager/docker-compose/extract extractPackageFile() extracts mu "currentValue": "before", "datasource": "docker", "depName": "dockersamples/examplevotingapp_result", + "packageName": "dockersamples/examplevotingapp_result", "replaceString": "dockersamples/examplevotingapp_result:before", }, { @@ -182,6 +201,7 @@ exports[`modules/manager/docker-compose/extract extractPackageFile() extracts mu "currentValue": undefined, "datasource": "docker", "depName": "dockersamples/examplevotingapp_worker", + "packageName": "dockersamples/examplevotingapp_worker", "replaceString": "dockersamples/examplevotingapp_worker", }, { @@ -190,6 +210,7 @@ exports[`modules/manager/docker-compose/extract extractPackageFile() extracts mu "currentValue": "stable", "datasource": "docker", "depName": "dockersamples/visualizer", + "packageName": "dockersamples/visualizer", "replaceString": "dockersamples/visualizer:stable", }, { diff --git a/lib/modules/manager/docker-compose/extract.spec.ts b/lib/modules/manager/docker-compose/extract.spec.ts index a089c8b465a612..b5d891083a3542 100644 --- a/lib/modules/manager/docker-compose/extract.spec.ts +++ b/lib/modules/manager/docker-compose/extract.spec.ts @@ -41,18 +41,18 @@ describe('modules/manager/docker-compose/extract', () => { it('extracts default variable values for version 3', () => { const res = extractPackageFile(yamlFile3DefaultValue, '', {}); - expect(res?.deps).toMatchInlineSnapshot(` - [ - { - "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", - "currentDigest": "sha256:abcd", - "currentValue": "5.0.0", - "datasource": "docker", - "depName": "redis", - "replaceString": "redis:5.0.0@sha256:abcd", - }, - ] - `); + expect(res?.deps).toEqual([ + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentDigest: 'sha256:abcd', + currentValue: '5.0.0', + datasource: 'docker', + depName: 'redis', + packageName: 'redis', + replaceString: 'redis:5.0.0@sha256:abcd', + }, + ]); expect(res?.deps).toHaveLength(1); }); @@ -72,6 +72,7 @@ describe('modules/manager/docker-compose/extract', () => { deps: [ { depName: 'node', + packageName: 'node', currentValue: '20.0.0', currentDigest: undefined, replaceString: 'node:20.0.0', @@ -103,7 +104,8 @@ describe('modules/manager/docker-compose/extract', () => { currentDigest: undefined, currentValue: '0.0.1', datasource: 'docker', - depName: 'my-quay-mirror.registry.com/nginx', + depName: 'quay.io/nginx', + packageName: 'my-quay-mirror.registry.com/nginx', replaceString: 'quay.io/nginx:0.0.1', }, ], @@ -131,6 +133,7 @@ describe('modules/manager/docker-compose/extract', () => { currentValue: '0.0.1', datasource: 'docker', depName: 'quay.io/nginx', + packageName: 'quay.io/nginx', replaceString: 'quay.io/nginx:0.0.1', }, ], @@ -158,7 +161,8 @@ describe('modules/manager/docker-compose/extract', () => { currentDigest: undefined, currentValue: '0.0.1', datasource: 'docker', - depName: 'my-quay-mirror.registry.com/nginx', + depName: 'quay.io/nginx', + packageName: 'my-quay-mirror.registry.com/nginx', replaceString: 'quay.io/nginx:0.0.1', }, ], @@ -184,6 +188,7 @@ describe('modules/manager/docker-compose/extract', () => { currentValue: '0.0.1', datasource: 'docker', depName: 'quay.io/nginx', + packageName: 'quay.io/nginx', replaceString: 'quay.io/nginx:0.0.1', }, ], diff --git a/lib/modules/manager/dockerfile/extract.spec.ts b/lib/modules/manager/dockerfile/extract.spec.ts index a2bcdab3feca0e..5278a679109ea4 100644 --- a/lib/modules/manager/dockerfile/extract.spec.ts +++ b/lib/modules/manager/dockerfile/extract.spec.ts @@ -26,6 +26,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: undefined, datasource: 'docker', depName: 'node', + packageName: 'node', depType: 'final', replaceString: 'node', }, @@ -50,6 +51,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: undefined, datasource: 'docker', depName: 'ghcr.io/astral-sh/uv', + packageName: 'ghcr.io/astral-sh/uv', depType: 'stage', replaceString: 'ghcr.io/astral-sh/uv', }, @@ -60,6 +62,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: undefined, datasource: 'docker', depName: 'example.com/cache/image', + packageName: 'example.com/cache/image', depType: 'final', replaceString: 'example.com/cache/image', }, @@ -76,6 +79,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: undefined, datasource: 'docker', depName: 'node', + packageName: 'node', depType: 'final', replaceString: 'node', }, @@ -92,6 +96,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '8.9.0-alpine', datasource: 'docker', depName: 'node', + packageName: 'node', depType: 'final', replaceString: 'node:8.9.0-alpine', }, @@ -113,6 +118,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: undefined, datasource: 'docker', depName: 'node', + packageName: 'node', depType: 'final', replaceString: 'node@sha256:eb85fc5b1198f5e1ec025ea07586bdbbf397e7d82df66c90d7511f533517e063', @@ -135,6 +141,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '8.9.0', datasource: 'docker', depName: 'node', + packageName: 'node', depType: 'final', replaceString: 'node:8.9.0@sha256:eb85fc5b1198f5e1ec025ea07586bdbbf397e7d82df66c90d7511f533517e063', @@ -156,6 +163,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '8.9.0-alpine', datasource: 'docker', depName: 'node', + packageName: 'node', depType: 'final', replaceString: 'node:8.9.0-alpine', }, @@ -176,6 +184,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: undefined, datasource: 'docker', depName: 'node', + packageName: 'node', depType: 'final', replaceString: 'node', }, @@ -196,6 +205,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '8', datasource: 'docker', depName: 'registry2.something.info/node', + packageName: 'registry2.something.info/node', depType: 'final', replaceString: 'registry2.something.info/node:8', }, @@ -216,6 +226,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '8-alpine', datasource: 'docker', depName: 'registry2.something.info/node', + packageName: 'registry2.something.info/node', depType: 'final', replaceString: 'registry2.something.info/node:8-alpine', }, @@ -236,6 +247,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '8', datasource: 'docker', depName: 'registry2.something.info:5005/node', + packageName: 'registry2.something.info:5005/node', depType: 'final', replaceString: 'registry2.something.info:5005/node:8', }, @@ -256,6 +268,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: undefined, datasource: 'docker', depName: 'registry2.something.info:5005/node', + packageName: 'registry2.something.info:5005/node', depType: 'final', replaceString: 'registry2.something.info:5005/node', }, @@ -289,6 +302,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '8', datasource: 'docker', depName: 'mynamespace/node', + packageName: 'mynamespace/node', depType: 'final', replaceString: 'mynamespace/node:8', }, @@ -309,6 +323,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '8', datasource: 'docker', depName: 'registry2.something.info/someaccount/node', + packageName: 'registry2.something.info/someaccount/node', depType: 'final', replaceString: 'registry2.something.info/someaccount/node:8', }, @@ -329,6 +344,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '8.7.0', datasource: 'docker', depName: 'registry.allmine.info:5005/node', + packageName: 'registry.allmine.info:5005/node', depType: 'final', replaceString: 'registry.allmine.info:5005/node:8.7.0', }, @@ -349,6 +365,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '6.12.3', datasource: 'docker', depName: 'node', + packageName: 'node', depType: 'stage', replaceString: 'node:6.12.3', }, @@ -359,6 +376,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '3.6-slim', datasource: 'docker', depName: 'python', + packageName: 'python', depType: 'final', replaceString: 'python:3.6-slim', }, @@ -379,6 +397,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '6.12.3', datasource: 'docker', depName: 'node', + packageName: 'node', depType: 'final', replaceString: 'node:6.12.3', }, @@ -404,6 +423,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '6.12.3', datasource: 'docker', depName: 'node', + packageName: 'node', depType: 'final', replaceString: 'node:6.12.3', }, @@ -424,6 +444,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: 'v0.11.0', datasource: 'docker', depName: 'gcr.io/k8s-skaffold/skaffold', + packageName: 'gcr.io/k8s-skaffold/skaffold', depType: 'final', replaceString: 'gcr.io/k8s-skaffold/skaffold:v0.11.0', }, @@ -448,6 +469,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: 'v0.11.0', datasource: 'docker', depName: 'gcr.io/k8s-skaffold/skaffold', + packageName: 'gcr.io/k8s-skaffold/skaffold', depType: 'final', replaceString: 'gcr.io/k8s-skaffold/skaffold:v0.11.0', }, @@ -469,6 +491,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '6.12.3', datasource: 'docker', depName: 'node', + packageName: 'node', depType: 'final', replaceString: 'node:6.12.3', }, @@ -489,6 +512,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '6.12.3', datasource: 'docker', depName: 'node', + packageName: 'node', depType: 'final', replaceString: 'node:6.12.3', }, @@ -509,6 +533,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '8.15.1-alpine', datasource: 'docker', depName: 'node', + packageName: 'node', depType: 'stage', replaceString: 'node:8.15.1-alpine', }, @@ -519,6 +544,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '1.23.3', datasource: 'docker', depName: 'golang', + packageName: 'golang', depType: 'stage', replaceString: 'golang:1.23.3', }, @@ -529,6 +555,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: 'latest', datasource: 'docker', depName: 'alpine', + packageName: 'alpine', depType: 'final', replaceString: 'alpine:latest', }, @@ -552,6 +579,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '8.11.3-alpine', datasource: 'docker', depName: 'node', + packageName: 'node', depType: 'stage', replaceString: 'node:8.11.3-alpine@sha256:d743b4141b02fcfb8beb68f92b4cd164f60ee457bf2d053f36785bf86de16b0d', @@ -563,6 +591,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '1.1.1', datasource: 'docker', depName: 'buildkite/puppeteer', + packageName: 'buildkite/puppeteer', depType: 'final', replaceString: 'buildkite/puppeteer:1.1.1', }, @@ -579,6 +608,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: undefined, datasource: 'docker', depName: 'image1', + packageName: 'image1', depType: 'stage', replaceString: 'image1', }, @@ -589,6 +619,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '1.0.0', datasource: 'docker', depName: 'image2', + packageName: 'image2', depType: 'stage', replaceString: 'image2:1.0.0@sha256:abcdef', }, @@ -599,6 +630,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: undefined, datasource: 'docker', depName: 'image4', + packageName: 'image4', depType: 'stage', replaceString: 'image4', }, @@ -609,6 +641,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: undefined, datasource: 'docker', depName: 'image5', + packageName: 'image5', depType: 'stage', replaceString: 'image5', }, @@ -619,6 +652,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: undefined, datasource: 'docker', depName: 'image6', + packageName: 'image6', depType: 'stage', replaceString: 'image6', }, @@ -629,6 +663,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '1.0.0', datasource: 'docker', depName: 'image7', + packageName: 'image7', depType: 'stage', replaceString: 'image7:1.0.0@sha256:abcdef', }, @@ -639,6 +674,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: undefined, datasource: 'docker', depName: 'image11', + packageName: 'image11', depType: 'stage', replaceString: 'image11', }, @@ -649,6 +685,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: undefined, datasource: 'docker', depName: 'image12', + packageName: 'image12', depType: 'stage', replaceString: 'image12', }, @@ -659,6 +696,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: undefined, datasource: 'docker', depName: 'image13', + packageName: 'image13', depType: 'final', replaceString: 'image13', }, @@ -675,6 +713,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: undefined, datasource: 'docker', depName: 'calico/node', + packageName: 'calico/node', depType: 'final', replaceString: 'calico/node', }, @@ -691,6 +730,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '18.04', datasource: 'docker', depName: 'ubuntu', + packageName: 'ubuntu', depType: 'final', replaceString: 'ubuntu:18.04', versioning: 'ubuntu', @@ -708,6 +748,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: 'buster', datasource: 'docker', depName: 'debian', + packageName: 'debian', depType: 'final', replaceString: 'debian:buster', versioning: 'debian', @@ -725,6 +766,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '11.4-slim', datasource: 'docker', depName: 'debian', + packageName: 'debian', depType: 'final', replaceString: 'debian:11.4-slim', }, @@ -763,6 +805,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '10', datasource: 'docker', depName: 'docker.io/library/debian', + packageName: 'docker.io/library/debian', depType: 'final', replaceString: 'docker.io/library/debian:10', versioning: 'debian', @@ -802,6 +845,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '18.04', datasource: 'docker', depName: 'public.ecr.aws/ubuntu/ubuntu', + packageName: 'public.ecr.aws/ubuntu/ubuntu', depType: 'final', replaceString: 'public.ecr.aws/ubuntu/ubuntu:18.04', versioning: 'ubuntu', @@ -823,6 +867,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '3.5', datasource: 'docker', depName: 'alpine', + packageName: 'alpine', depType: 'final', replaceString: 'alpine:3.5', }, @@ -839,6 +884,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '1.20', datasource: 'docker', depName: 'nginx', + packageName: 'nginx', depType: 'final', replaceString: 'nginx:1.20', }, @@ -877,6 +923,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '1.20', datasource: 'docker', depName: 'nginx', + packageName: 'nginx', depType: 'final', replaceString: 'FROM nginx:1.20${patch1}$patch2\n', }, @@ -897,6 +944,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '1.60.0-bullseye', datasource: 'docker', depName: 'rust', + packageName: 'rust', depType: 'final', replaceString: 'ARG\tVARIANT="1.60.0-bullseye" \n', }, @@ -916,6 +964,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: 'xenial', datasource: 'docker', depName: 'ubuntu', + packageName: 'ubuntu', depType: 'final', replaceString: 'ARG IMAGE_VERSION=${IMAGE_VERSION:-ubuntu:xenial}\n', versioning: 'ubuntu', @@ -939,6 +988,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: undefined, datasource: 'docker', depName: 'gcr.io/distroless/java17', + packageName: 'gcr.io/distroless/java17', depType: 'final', replaceString: 'ARG sha_digest=sha256:ab37242e81cbc031b2600eef4440fe87055a05c14b40686df85078cc5086c98f', @@ -960,6 +1010,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '1.19', datasource: 'docker', depName: 'nginx', + packageName: 'nginx', depType: 'stage', replaceString: 'ARG base=nginx:1.19\n', }, @@ -970,6 +1021,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '1.20', datasource: 'docker', depName: 'nginx', + packageName: 'nginx', depType: 'final', replaceString: 'ARG base=nginx:1.20\n', }, @@ -990,6 +1042,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '9.2-devel-ubuntu16.04', datasource: 'docker', depName: 'nvidia/cuda', + packageName: 'nvidia/cuda', depType: 'final', replaceString: 'nvidia/cuda:9.2-devel-ubuntu16.04', }, @@ -1017,6 +1070,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '3.15.4', datasource: 'docker', depName: 'alpine', + packageName: 'alpine', depType: 'stage', replaceString: ' ARG \\\n' + @@ -1035,6 +1089,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '1.18.0-alpine', datasource: 'docker', depName: 'nginx', + packageName: 'nginx', depType: 'final', replaceString: 'ARG \\\n' + @@ -1060,6 +1115,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '1.20', datasource: 'docker', depName: 'nginx', + packageName: 'nginx', depType: 'final', replaceString: 'nginx:1.20', }, @@ -1076,6 +1132,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '1', datasource: 'docker', depName: 'docker/dockerfile', + packageName: 'docker/dockerfile', depType: 'syntax', replaceString: 'docker/dockerfile:1', }, @@ -1088,6 +1145,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '3.15.4', datasource: 'docker', depName: 'alpine', + packageName: 'alpine', depType: 'stage', replaceString: ' ARG `\n' + @@ -1105,6 +1163,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '18.04', datasource: 'docker', depName: 'nginx', + packageName: 'nginx', depType: 'stage', replaceString: 'ARG `\n' + @@ -1120,6 +1179,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: undefined, datasource: 'docker', depName: 'image5', + packageName: 'image5', depType: 'stage', replaceString: 'image5', }, @@ -1130,6 +1190,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: undefined, datasource: 'docker', depName: 'image12', + packageName: 'image12', depType: 'final', replaceString: 'image12', }, @@ -1150,6 +1211,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: 'nonroot', datasource: 'docker', depName: 'gcr.io/distroless/static-debian11', + packageName: 'gcr.io/distroless/static-debian11', depType: 'final', replaceString: 'ARG REF_NAME=${REF_NAME:-"gcr.io/distroless/static-debian11:nonroot@sha256:abc"}', @@ -1171,6 +1233,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '14.04', datasource: 'docker', depName: 'ubuntu', + packageName: 'ubuntu', depType: 'final', replaceString: 'ARG IMAGE_TAG=14.04\r\n#something unrelated\r\nFROM ubuntu:$IMAGE_TAG@sha256:abc', @@ -1203,6 +1266,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '16.14.2-alpine3.14', datasource: 'docker', depName: 'docker.io/library/node', + packageName: 'docker.io/library/node', depType: 'final', replaceString: 'ARG NODE_IMAGE_HASH="@sha256:ba9c961513b853210ae0ca1524274eafa5fd94e20b856343887ca7274c8450e4"\n' + @@ -1229,6 +1293,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '0.6.2', datasource: 'docker', depName: 'quay.io/myName/myPackage', + packageName: 'quay.io/myName/myPackage', depType: 'final', replaceString: 'quay.io/myName/myPackage:0.6.2', }, @@ -1255,7 +1320,8 @@ describe('modules/manager/dockerfile/extract', () => { currentDigest: undefined, currentValue: '0.6.2', datasource: 'docker', - depName: 'my-quay-mirror.registry.com/myName/myPackage', + depName: 'quay.io/myName/myPackage', + packageName: 'my-quay-mirror.registry.com/myName/myPackage', depType: 'final', replaceString: 'quay.io/myName/myPackage:0.6.2', }, @@ -1263,6 +1329,33 @@ describe('modules/manager/dockerfile/extract', () => { }); }); + it('replaces registry alias from start only', () => { + const res = extractPackageFile( + 'FROM index.docker.io/myName/myPackage:0.6.2\n', + 'Dockerfile', + { + registryAliases: { + 'docker.io': 'my-docker-mirror.registry.com', + }, + }, + ); + expect(res).toEqual({ + deps: [ + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentDigest: undefined, + currentValue: '0.6.2', + datasource: 'docker', + depName: 'index.docker.io/myName/myPackage', + packageName: 'index.docker.io/myName/myPackage', + depType: 'final', + replaceString: 'index.docker.io/myName/myPackage:0.6.2', + }, + ], + }); + }); + it('handles empty registry', () => { const res = extractPackageFile( 'FROM myName/myPackage:0.6.2\n', @@ -1283,6 +1376,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '0.6.2', datasource: 'docker', depName: 'myName/myPackage', + packageName: 'myName/myPackage', depType: 'final', replaceString: 'myName/myPackage:0.6.2', }, @@ -1305,6 +1399,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '1.1.7', datasource: 'docker', depName: 'docker/dockerfile', + packageName: 'docker/dockerfile', depType: 'syntax', replaceString: 'docker/dockerfile:1.1.7', }, @@ -1315,6 +1410,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '3.13.5', datasource: 'docker', depName: 'alpine', + packageName: 'alpine', depType: 'final', replaceString: 'alpine:3.13.5', }, @@ -1337,6 +1433,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '3.13.5', datasource: 'docker', depName: 'alpine', + packageName: 'alpine', depType: 'final', replaceString: 'alpine:3.13.5', }, @@ -1362,6 +1459,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '5.0.0', datasource: 'docker', depName: 'redis', + packageName: 'redis', replaceString: 'redis:5.0.0@sha256:abcd', }); @@ -1372,6 +1470,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: '5.0.0', datasource: 'docker', depName: 'redis', + packageName: 'redis', replaceString: 'redis:5.0.0', }); @@ -1382,6 +1481,7 @@ describe('modules/manager/dockerfile/extract', () => { currentDigest: 'sha256:abcd', datasource: 'docker', depName: 'redis', + packageName: 'redis', replaceString: 'redis@sha256:abcd', }); @@ -1395,6 +1495,7 @@ describe('modules/manager/dockerfile/extract', () => { currentValue: 'nonroot', datasource: 'docker', depName: 'gcr.io/distroless/static-debian11', + packageName: 'gcr.io/distroless/static-debian11', replaceString: 'gcr.io/distroless/static-debian11:nonroot@sha256:abc', }); @@ -1473,9 +1574,10 @@ describe('modules/manager/dockerfile/extract', () => { it.each` name | registryAliases | imageName | dep - ${'multiple aliases'} | ${{ foo: 'foo.registry.com', bar: 'bar.registry.com' }} | ${'foo/image:1.0'} | ${{ depName: 'foo.registry.com/image', currentValue: '1.0', autoReplaceStringTemplate: `foo/image${versionAndDigestTemplate}` }} - ${'aliased variable'} | ${{ $CI_REGISTRY: 'registry.com' }} | ${'$CI_REGISTRY/image:1.0'} | ${{ depName: 'registry.com/image', currentValue: '1.0', autoReplaceStringTemplate: `$CI_REGISTRY/image${versionAndDigestTemplate}` }} - ${'variables with brackets'} | ${{ '${CI_REGISTRY}': 'registry.com' }} | ${'${CI_REGISTRY}/image:1.0'} | ${{ depName: 'registry.com/image', currentValue: '1.0', autoReplaceStringTemplate: `$\{CI_REGISTRY}/image${versionAndDigestTemplate}` }} + ${'simple aliases'} | ${{ 'foo.com/some': 'foo.registry.com' }} | ${'foo.com/some/image:1.0'} | ${{ depName: 'foo.com/some/image', packageName: 'foo.registry.com/image', currentValue: '1.0', autoReplaceStringTemplate: `foo.com/some/image${versionAndDigestTemplate}` }} + ${'multiple aliases'} | ${{ foo: 'foo.registry.com', bar: 'bar.registry.com' }} | ${'foo/image:1.0'} | ${{ depName: 'foo/image', packageName: 'foo.registry.com/image', currentValue: '1.0', autoReplaceStringTemplate: `foo/image${versionAndDigestTemplate}` }} + ${'aliased variable'} | ${{ $CI_REGISTRY: 'registry.com' }} | ${'$CI_REGISTRY/image:1.0'} | ${{ depName: '$CI_REGISTRY/image', packageName: 'registry.com/image', currentValue: '1.0', autoReplaceStringTemplate: `$CI_REGISTRY/image${versionAndDigestTemplate}` }} + ${'variables with brackets'} | ${{ '${CI_REGISTRY}': 'registry.com' }} | ${'${CI_REGISTRY}/image:1.0'} | ${{ depName: '${CI_REGISTRY}/image', packageName: 'registry.com/image', currentValue: '1.0', autoReplaceStringTemplate: `$\{CI_REGISTRY}/image${versionAndDigestTemplate}` }} ${'not aliased variable'} | ${{}} | ${'$CI_REGISTRY/image:1.0'} | ${{ autoReplaceStringTemplate: defaultAutoReplaceStringTemplate }} ${'plain image'} | ${{}} | ${'registry.com/image:1.0'} | ${{ depName: 'registry.com/image', currentValue: '1.0', autoReplaceStringTemplate: defaultAutoReplaceStringTemplate }} `( diff --git a/lib/modules/manager/dockerfile/extract.ts b/lib/modules/manager/dockerfile/extract.ts index 019e7ee86a1653..72261368268701 100644 --- a/lib/modules/manager/dockerfile/extract.ts +++ b/lib/modules/manager/dockerfile/extract.ts @@ -1,6 +1,6 @@ import is from '@sindresorhus/is'; import { logger } from '../../../logger'; -import { escapeRegExp, newlineRegex, regEx } from '../../../util/regex'; +import { newlineRegex, regEx } from '../../../util/regex'; import { DockerDatasource } from '../../datasource/docker'; import * as debianVersioning from '../../versioning/debian'; import * as ubuntuVersioning from '../../versioning/ubuntu'; @@ -141,6 +141,7 @@ export function splitImageParts(currentFrom: string): PackageDependency { const dep: PackageDependency = { depName, + packageName: depName, currentValue, currentDigest, }; @@ -167,7 +168,10 @@ export function getDep( specifyReplaceString = true, registryAliases?: Record, ): PackageDependency { - if (!is.string(currentFrom) || is.emptyStringOrWhitespace(currentFrom)) { + if ( + !is.string(currentFrom) || + !is.nonEmptyStringAndNotWhitespace(currentFrom) + ) { return { skipReason: 'invalid-value', }; @@ -175,16 +179,20 @@ export function getDep( // Resolve registry aliases first so that we don't need special casing later on: for (const [name, value] of Object.entries(registryAliases ?? {})) { - const escapedName = escapeRegExp(name); - const groups = regEx(`(?${escapedName})/(?.+)`).exec( - currentFrom, - )?.groups; - if (groups) { + if (currentFrom.startsWith(`${name}/`)) { + const depName = currentFrom.substring(name.length + 1); const dep = { - ...getDep(`${value}/${groups.depName}`), + ...getDep(`${value}/${depName}`, false), replaceString: currentFrom, }; - dep.autoReplaceStringTemplate = getAutoReplaceTemplate(dep); + // retain depName, not sure if condition is necessary + if (dep.depName?.startsWith(value)) { + dep.packageName = dep.depName; + dep.depName = `${name}/${dep.depName.substring(value.length + 1)}`; + } + if (specifyReplaceString) { + dep.autoReplaceStringTemplate = getAutoReplaceTemplate(dep); + } return dep; } } @@ -204,7 +212,6 @@ export function getDep( const specialPrefixes = ['amd64', 'arm64', 'library']; for (const prefix of specialPrefixes) { if (dep.depName.startsWith(`${prefix}/`)) { - dep.packageName = dep.depName; dep.depName = dep.depName.replace(`${prefix}/`, ''); if (specifyReplaceString) { dep.autoReplaceStringTemplate = @@ -229,7 +236,6 @@ export function getDep( if (dep.depName && quayRegex.test(dep.depName)) { const depName = dep.depName.replace(quayRegex, 'quay.io'); if (depName !== dep.depName) { - dep.packageName = dep.depName; dep.depName = depName; dep.autoReplaceStringTemplate = '{{packageName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}'; diff --git a/lib/modules/manager/droneci/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/droneci/__snapshots__/extract.spec.ts.snap index 3d757597bec4e8..a5b0403ea1628c 100644 --- a/lib/modules/manager/droneci/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/droneci/__snapshots__/extract.spec.ts.snap @@ -9,6 +9,7 @@ exports[`modules/manager/droneci/extract extractPackageFile() extracts multiple "datasource": "docker", "depName": "elixir", "depType": "docker", + "packageName": "elixir", "replaceString": "elixir:1.8.1-alpine", }, { @@ -28,6 +29,7 @@ exports[`modules/manager/droneci/extract extractPackageFile() extracts multiple "datasource": "docker", "depName": "mysql", "depType": "docker", + "packageName": "mysql", "replaceString": "mysql:5.7.24", }, { @@ -37,6 +39,7 @@ exports[`modules/manager/droneci/extract extractPackageFile() extracts multiple "datasource": "docker", "depName": "redis", "depType": "docker", + "packageName": "redis", "replaceString": "redis:alpine", }, { diff --git a/lib/modules/manager/droneci/extract.spec.ts b/lib/modules/manager/droneci/extract.spec.ts index f2dc05ea53ad58..1875c6dfb56f0e 100644 --- a/lib/modules/manager/droneci/extract.spec.ts +++ b/lib/modules/manager/droneci/extract.spec.ts @@ -31,7 +31,8 @@ describe('modules/manager/droneci/extract', () => { currentDigest: undefined, currentValue: '1.8.1-alpine', datasource: 'docker', - depName: 'my-quay-mirror.registry.com/elixir', + depName: 'quay.io/elixir', + packageName: 'my-quay-mirror.registry.com/elixir', replaceString: 'quay.io/elixir:1.8.1-alpine', depType: 'docker', }, @@ -54,6 +55,7 @@ describe('modules/manager/droneci/extract', () => { currentValue: '1.8.1-alpine', datasource: 'docker', depName: 'quay.io/elixir', + packageName: 'quay.io/elixir', replaceString: 'quay.io/elixir:1.8.1-alpine', depType: 'docker', }, @@ -76,7 +78,8 @@ describe('modules/manager/droneci/extract', () => { currentDigest: undefined, currentValue: '1.8.1-alpine', datasource: 'docker', - depName: 'my-quay-mirror.registry.com/elixir', + depName: 'quay.io/elixir', + packageName: 'my-quay-mirror.registry.com/elixir', replaceString: 'quay.io/elixir:1.8.1-alpine', depType: 'docker', }, diff --git a/lib/modules/manager/fleet/extract.ts b/lib/modules/manager/fleet/extract.ts index a84dfa3a04756b..6470f40b4dc511 100644 --- a/lib/modules/manager/fleet/extract.ts +++ b/lib/modules/manager/fleet/extract.ts @@ -61,8 +61,6 @@ function extractFleetHelmBlock(doc: FleetHelmBlock): PackageDependency { 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, diff --git a/lib/modules/manager/flux/__fixtures__/multidoc.yaml b/lib/modules/manager/flux/__fixtures__/multidoc.yaml index 675feac3fc5860..9fdea372428dd6 100644 --- a/lib/modules/manager/flux/__fixtures__/multidoc.yaml +++ b/lib/modules/manager/flux/__fixtures__/multidoc.yaml @@ -13,6 +13,10 @@ spec: name: external-dns version: "1.7.0" interval: 1h0m0s + values: + image: + repository: k8s.gcr.io/external-dns/external-dns + tag: v0.13.4 --- apiVersion: source.toolkit.fluxcd.io/v1beta1 kind: HelmRepository diff --git a/lib/modules/manager/flux/extract.spec.ts b/lib/modules/manager/flux/extract.spec.ts index 134fc4dd6ed73e..eb90d5add9e6fb 100644 --- a/lib/modules/manager/flux/extract.spec.ts +++ b/lib/modules/manager/flux/extract.spec.ts @@ -34,6 +34,17 @@ describe('modules/manager/flux/extract', () => { depName: 'external-dns', registryUrls: ['https://kubernetes-sigs.github.io/external-dns/'], }, + { + autoReplaceStringTemplate: + '{{newValue}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentDigest: undefined, + currentValue: 'v0.13.4', + datasource: DockerDatasource.id, + depName: 'k8s.gcr.io/external-dns/external-dns', + packageName: 'k8s.gcr.io/external-dns/external-dns', + replaceString: 'v0.13.4', + versioning: DockerDatasource.id, + }, { currentValue: 'v11.35.4', datasource: GithubTagsDatasource.id, @@ -48,6 +59,7 @@ describe('modules/manager/flux/extract', () => { currentValue: 'v1.8.2', datasource: DockerDatasource.id, depName: 'ghcr.io/kyverno/manifests/kyverno', + packageName: 'ghcr.io/kyverno/manifests/kyverno', replaceString: 'v1.8.2', }, ], @@ -534,6 +546,7 @@ describe('modules/manager/flux/extract', () => { currentValue: undefined, datasource: 'docker', depName: 'ghcr.io/kyverno/manifests/kyverno', + packageName: 'ghcr.io/kyverno/manifests/kyverno', skipReason: 'unversioned-reference', }, ], @@ -567,7 +580,8 @@ describe('modules/manager/flux/extract', () => { '{{#if newValue}}{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', currentValue: 'v1.8.2', currentDigest: undefined, - depName: 'ghcr.proxy.test/some/path/kyverno/manifests/kyverno', + depName: 'ghcr.io/kyverno/manifests/kyverno', + packageName: 'ghcr.proxy.test/some/path/kyverno/manifests/kyverno', datasource: DockerDatasource.id, replaceString: 'v1.8.2', }, @@ -596,6 +610,7 @@ describe('modules/manager/flux/extract', () => { currentDigest: 'sha256:761c3189c482d0f1f0ad3735ca05c4c398cae201d2169f6645280c7b7b2ce6fc', depName: 'ghcr.io/kyverno/manifests/kyverno', + packageName: 'ghcr.io/kyverno/manifests/kyverno', datasource: DockerDatasource.id, }, ], @@ -626,6 +641,7 @@ describe('modules/manager/flux/extract', () => { 'sha256:761c3189c482d0f1f0ad3735ca05c4c398cae201d2169f6645280c7b7b2ce6fc', currentValue: 'v1.8.2', depName: 'ghcr.io/kyverno/manifests/kyverno', + packageName: 'ghcr.io/kyverno/manifests/kyverno', datasource: DockerDatasource.id, replaceString: 'v1.8.2@sha256:761c3189c482d0f1f0ad3735ca05c4c398cae201d2169f6645280c7b7b2ce6fc', @@ -657,6 +673,7 @@ describe('modules/manager/flux/extract', () => { 'sha256:761c3189c482d0f1f0ad3735ca05c4c398cae201d2169f6645280c7b7b2ce6fc', datasource: DockerDatasource.id, depName: 'ghcr.io/kyverno/manifests/kyverno', + packageName: 'ghcr.io/kyverno/manifests/kyverno', }, ], }); @@ -693,6 +710,7 @@ describe('modules/manager/flux/extract', () => { currentValue: 'v1', datasource: 'docker', depName: 'my-registry/podinfo', + packageName: 'my-registry/podinfo', replaceString: 'v1', }, { @@ -702,6 +720,7 @@ describe('modules/manager/flux/extract', () => { currentValue: '1.8.0', datasource: 'docker', depName: 'podinfo', + packageName: 'podinfo', replaceString: '1.8.0', }, { @@ -709,6 +728,7 @@ describe('modules/manager/flux/extract', () => { currentValue: undefined, datasource: 'docker', depName: 'my-podinfo', + packageName: 'my-podinfo', replaceString: 'my-podinfo', }, { @@ -717,6 +737,7 @@ describe('modules/manager/flux/extract', () => { currentValue: undefined, datasource: 'docker', depName: 'podinfo', + packageName: 'podinfo', replaceString: 'sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3', }, @@ -796,6 +817,7 @@ describe('modules/manager/flux/extract', () => { currentDigest: undefined, currentValue: 'v1.8.2', depName: 'ghcr.io/kyverno/manifests/kyverno', + packageName: 'ghcr.io/kyverno/manifests/kyverno', datasource: DockerDatasource.id, replaceString: 'v1.8.2', }, diff --git a/lib/modules/manager/flux/extract.ts b/lib/modules/manager/flux/extract.ts index 98e10778b09785..85a0a13b92421f 100644 --- a/lib/modules/manager/flux/extract.ts +++ b/lib/modules/manager/flux/extract.ts @@ -13,6 +13,7 @@ import { GithubTagsDatasource } from '../../datasource/github-tags'; import { GitlabTagsDatasource } from '../../datasource/gitlab-tags'; import { HelmDatasource } from '../../datasource/helm'; import { getDep } from '../dockerfile/extract'; +import { findDependencies } from '../helm-values/extract'; import { isOCIRegistry, removeOCIPrefix } from '../helmv3/oci'; import { extractImage } from '../kustomize/extract'; import type { @@ -122,7 +123,7 @@ function resolveHelmRepository( `${removeOCIPrefix(repo.spec.url)}/${dep.depName}`, false, registryAliases, - ).depName; + ).packageName; return null; } else { return repo.spec.url; @@ -187,6 +188,10 @@ function resolveResourceManifest( ); resolveHelmRepository(dep, matchingRepositories, registryAliases); deps.push(dep); + + if (resource.spec.values) { + deps.push(...findDependencies(resource.spec.values, registryAliases)); + } break; } case 'GitRepository': { diff --git a/lib/modules/manager/flux/readme.md b/lib/modules/manager/flux/readme.md index bb1777f3bd8f05..508331962138f0 100644 --- a/lib/modules/manager/flux/readme.md +++ b/lib/modules/manager/flux/readme.md @@ -22,6 +22,8 @@ Namespaces will not be inferred from the context (e.g. from the parent `Kustomiz Renovate updates `HelmRelease` resources coming from `GitRepository` by updating the `GitRepository` resource. +Renovate updates Docker dependencies inside `HelmRelease` `values` like the [`helm-values`](../helm-values/index.md) manager. + ### GitRepository support Renovate can update `git` references from `GitRepository` resources. diff --git a/lib/modules/manager/flux/schema.ts b/lib/modules/manager/flux/schema.ts index 8e4870b2245bf3..d76ff8fb5b2222 100644 --- a/lib/modules/manager/flux/schema.ts +++ b/lib/modules/manager/flux/schema.ts @@ -29,6 +29,7 @@ export const HelmRelease = KubernetesResource.extend({ .optional(), }), }), + values: z.record(z.unknown()).optional(), }), }); diff --git a/lib/modules/manager/git-submodules/artifacts.ts b/lib/modules/manager/git-submodules/artifacts.ts index 531588df13c5f0..c8f6c334771c2b 100644 --- a/lib/modules/manager/git-submodules/artifacts.ts +++ b/lib/modules/manager/git-submodules/artifacts.ts @@ -7,7 +7,7 @@ export default function updateArtifacts({ const res: UpdateArtifactsResult[] = []; updatedDeps.forEach((dep) => { // TODO: types (#22198) - logger.info(`Updating submodule ${dep.depName}`); + logger.debug(`Updating submodule ${dep.depName}`); res.push({ file: { type: 'addition', path: dep.depName!, contents: '' }, }); 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 713985a87545cf..2d48fac1dd33b3 100644 --- a/lib/modules/manager/github-actions/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/github-actions/__snapshots__/extract.spec.ts.snap @@ -135,6 +135,7 @@ exports[`modules/manager/github-actions/extract extractPackageFile() extracts mu "datasource": "docker", "depName": "replicated/dockerfilelint", "depType": "docker", + "packageName": "replicated/dockerfilelint", "replaceString": "replicated/dockerfilelint", }, { @@ -154,6 +155,7 @@ exports[`modules/manager/github-actions/extract extractPackageFile() extracts mu "datasource": "docker", "depName": "node", "depType": "docker", + "packageName": "node", "replaceString": "node:6@sha256:7b65413af120ec5328077775022c78101f103258a1876ec2f83890bce416e896", }, { @@ -199,6 +201,7 @@ exports[`modules/manager/github-actions/extract extractPackageFile() extracts mu "datasource": "docker", "depName": "node", "depType": "container", + "packageName": "node", "replaceString": "node:16-bullseye", }, { @@ -208,6 +211,7 @@ exports[`modules/manager/github-actions/extract extractPackageFile() extracts mu "datasource": "docker", "depName": "redis", "depType": "service", + "packageName": "redis", "replaceString": "redis:5", }, { @@ -217,6 +221,7 @@ exports[`modules/manager/github-actions/extract extractPackageFile() extracts mu "datasource": "docker", "depName": "postgres", "depType": "service", + "packageName": "postgres", "replaceString": "postgres:10", }, { @@ -235,6 +240,7 @@ exports[`modules/manager/github-actions/extract extractPackageFile() extracts mu "datasource": "docker", "depName": "node", "depType": "container", + "packageName": "node", "replaceString": "node:16-bullseye", }, { diff --git a/lib/modules/manager/gitlabci/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/gitlabci/__snapshots__/extract.spec.ts.snap index dc093e8051b411..46e96348ee1863 100644 --- a/lib/modules/manager/gitlabci/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/gitlabci/__snapshots__/extract.spec.ts.snap @@ -11,6 +11,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "ruby", "depType": "image", + "packageName": "ruby", "replaceString": "ruby:2.5.0", }, { @@ -20,6 +21,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "hadolint/hadolint", "depType": "image", + "packageName": "hadolint/hadolint", "replaceString": "hadolint/hadolint:latest", }, { @@ -29,6 +31,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "docker", "depType": "image", + "packageName": "docker", "replaceString": "docker:latest", }, { @@ -38,6 +41,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "docker", "depType": "service-image", + "packageName": "docker", "replaceString": "docker:dind", }, { @@ -47,6 +51,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "docker", "depType": "image", + "packageName": "docker", "replaceString": "docker:latest", }, { @@ -56,6 +61,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "docker", "depType": "service-image", + "packageName": "docker", "replaceString": "docker:dind", }, { @@ -65,6 +71,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "image-name-test", "depType": "image-name", + "packageName": "image-name-test", "replaceString": "image-name-test:1.15", }, { @@ -74,6 +81,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "image-name-test", "depType": "image-name", + "packageName": "image-name-test", "replaceString": "image-name-test:1.15", }, ], @@ -93,6 +101,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "renovate/renovate", "depType": "image-name", + "packageName": "renovate/renovate", "replaceString": "renovate/renovate:19.70.8-slim", }, { @@ -102,6 +111,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "mariadb", "depType": "service-image", + "packageName": "mariadb", "replaceString": "mariadb:10.4.11", }, { @@ -111,6 +121,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "other/image", "depType": "service-image", + "packageName": "other/image", "replaceString": "other/image:1.0.0", }, ], @@ -130,6 +141,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "renovate/renovate", "depType": "image-name", + "packageName": "renovate/renovate", "replaceString": "renovate/renovate:19.70.8-slim", }, { @@ -139,6 +151,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "mariadb", "depType": "service-image", + "packageName": "mariadb", "replaceString": "mariadb:10.4.11", }, { @@ -148,6 +161,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "other/image", "depType": "service-image", + "packageName": "other/image", "replaceString": "other/image:1.0.0", }, ], @@ -162,6 +176,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "node", "depType": "image", + "packageName": "node", "replaceString": "node:12", }, ], @@ -176,6 +191,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "alpine", "depType": "image", + "packageName": "alpine", "replaceString": "alpine:3.11", }, ], @@ -195,6 +211,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "renovate/renovate", "depType": "image-name", + "packageName": "renovate/renovate", "replaceString": "renovate/renovate:19.70.8-slim", }, { @@ -204,6 +221,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "other/image1", "depType": "service-image", + "packageName": "other/image1", "replaceString": "other/image1:1.0.0", }, { @@ -213,6 +231,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "other/image2", "depType": "service-image", + "packageName": "other/image2", "replaceString": "other/image2:1.0.0", }, { @@ -222,6 +241,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "mooseagency/postgresql", "depType": "service-image", + "packageName": "mooseagency/postgresql", "replaceString": "mooseagency/postgresql:12.3-1@sha256:a5a65569456f221ee1f8a0b3b4e2d440eb5830772d9440c9b30b1dbfd454c778", }, { @@ -231,6 +251,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "mariadb", "depType": "service-image", + "packageName": "mariadb", "replaceString": "mariadb:10.4.11", }, { @@ -240,6 +261,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "postgres", "depType": "service-image", + "packageName": "postgres", "replaceString": "postgres:11.7", }, { @@ -249,6 +271,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "redis", "depType": "service-image", + "packageName": "redis", "replaceString": "redis:latest", }, { @@ -258,6 +281,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "registry.example.com/myimage", "depType": "service-image", + "packageName": "registry.example.com/myimage", "replaceString": "registry.example.com/myimage:latest", }, { @@ -267,6 +291,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "myimage", "depType": "service-image", + "packageName": "myimage", "replaceString": "myimage@sha256:0ecb2ad60", }, { @@ -276,6 +301,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts mult "datasource": "docker", "depName": "tomcat", "depType": "service-image", + "packageName": "tomcat", "replaceString": "tomcat:7-jre8", }, ], @@ -295,6 +321,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts name "datasource": "docker", "depName": "renovate/renovate", "depType": "image-name", + "packageName": "renovate/renovate", "replaceString": "renovate/renovate:19.70.8-slim", }, { @@ -304,6 +331,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts name "datasource": "docker", "depName": "mariadb", "depType": "service-image", + "packageName": "mariadb", "replaceString": "mariadb:10.4.11", }, { @@ -313,6 +341,7 @@ exports[`modules/manager/gitlabci/extract extractAllPackageFiles() extracts name "datasource": "docker", "depName": "other/image", "depType": "service-image", + "packageName": "other/image", "replaceString": "other/image:1.0.0", }, ], diff --git a/lib/modules/manager/gitlabci/extract.spec.ts b/lib/modules/manager/gitlabci/extract.spec.ts index 750273089e2738..19db324a3f444f 100644 --- a/lib/modules/manager/gitlabci/extract.spec.ts +++ b/lib/modules/manager/gitlabci/extract.spec.ts @@ -197,6 +197,7 @@ describe('modules/manager/gitlabci/extract', () => { currentValue: '31.65.1-slim', datasource: 'docker', depName: 'renovate/renovate', + packageName: 'renovate/renovate', depType: 'image-name', replaceString: '${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/renovate/renovate:31.65.1-slim', @@ -209,6 +210,7 @@ describe('modules/manager/gitlabci/extract', () => { currentValue: '10.4.11', datasource: 'docker', depName: 'mariadb', + packageName: 'mariadb', depType: 'service-image', replaceString: '$CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX/mariadb:10.4.11', @@ -221,6 +223,7 @@ describe('modules/manager/gitlabci/extract', () => { currentValue: '1.0.0', datasource: 'docker', depName: 'other/image1', + packageName: 'other/image1', depType: 'service-image', replaceString: '$CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/other/image1:1.0.0', @@ -257,7 +260,8 @@ describe('modules/manager/gitlabci/extract', () => { currentDigest: undefined, currentValue: '31.65.1-slim', datasource: 'docker', - depName: 'registry.com/renovate/renovate', + depName: '$CI_REGISTRY/renovate/renovate', + packageName: 'registry.com/renovate/renovate', depType: 'image-name', replaceString: '$CI_REGISTRY/renovate/renovate:31.65.1-slim', }, @@ -267,7 +271,8 @@ describe('modules/manager/gitlabci/extract', () => { currentDigest: undefined, currentValue: '10.4.11', datasource: 'docker', - depName: 'foo.registry.com/mariadb', + depName: 'foo/mariadb', + packageName: 'foo.registry.com/mariadb', depType: 'service-image', replaceString: 'foo/mariadb:10.4.11', }, @@ -277,7 +282,8 @@ describe('modules/manager/gitlabci/extract', () => { currentDigest: undefined, currentValue: '1.0.0', datasource: 'docker', - depName: 'registry.com/other/image1', + depName: '$CI_REGISTRY/other/image1', + packageName: 'registry.com/other/image1', depType: 'service-image', replaceString: '$CI_REGISTRY/other/image1:1.0.0', }, @@ -287,7 +293,8 @@ describe('modules/manager/gitlabci/extract', () => { currentDigest: undefined, currentValue: '1.0.0', datasource: 'docker', - depName: 'registry.com/build-images/image2', + depName: '$BUILD_IMAGES/image2', + packageName: 'registry.com/build-images/image2', depType: 'service-image', replaceString: '$BUILD_IMAGES/image2:1.0.0', }, @@ -302,6 +309,7 @@ describe('modules/manager/gitlabci/extract', () => { currentValue: 'test', datasource: 'docker', depName: 'image', + packageName: 'image', depType: 'image', replaceString: 'image:test', }; @@ -327,6 +335,7 @@ describe('modules/manager/gitlabci/extract', () => { currentValue: 'test', datasource: 'docker', depName: 'image', + packageName: 'image', depType: 'service-image', replaceString: 'image:test', }, @@ -337,6 +346,7 @@ describe('modules/manager/gitlabci/extract', () => { currentValue: 'test2', datasource: 'docker', depName: 'image2', + packageName: 'image2', depType: 'service-image', replaceString: 'image2:test2', }, @@ -358,6 +368,7 @@ describe('modules/manager/gitlabci/extract', () => { currentValue: 'test', datasource: 'docker', depName: 'image', + packageName: 'image', depType: 'image', replaceString: 'image:test', }, diff --git a/lib/modules/manager/gitlabci/utils.spec.ts b/lib/modules/manager/gitlabci/utils.spec.ts index 0b89036dd8f5df..b8d10547def541 100644 --- a/lib/modules/manager/gitlabci/utils.spec.ts +++ b/lib/modules/manager/gitlabci/utils.spec.ts @@ -47,9 +47,9 @@ describe('modules/manager/gitlabci/utils', () => { it.each` name | registryAliases | imageName | dep - ${'multiple aliases'} | ${{ foo: 'foo.registry.com', bar: 'bar.registry.com' }} | ${'foo/image:1.0'} | ${{ depName: 'foo.registry.com/image', currentValue: '1.0', autoReplaceStringTemplate: `foo/image${versionAndDigestTemplate}` }} - ${'aliased variable'} | ${{ $CI_REGISTRY: 'registry.com' }} | ${'$CI_REGISTRY/image:1.0'} | ${{ depName: 'registry.com/image', currentValue: '1.0', autoReplaceStringTemplate: `$CI_REGISTRY/image${versionAndDigestTemplate}` }} - ${'variables with brackets'} | ${{ '${CI_REGISTRY}': 'registry.com' }} | ${'${CI_REGISTRY}/image:1.0'} | ${{ depName: 'registry.com/image', currentValue: '1.0', autoReplaceStringTemplate: `$\{CI_REGISTRY}/image${versionAndDigestTemplate}` }} + ${'multiple aliases'} | ${{ foo: 'foo.registry.com', bar: 'bar.registry.com' }} | ${'foo/image:1.0'} | ${{ depName: 'foo/image', packageName: 'foo.registry.com/image', currentValue: '1.0', autoReplaceStringTemplate: `foo/image${versionAndDigestTemplate}` }} + ${'aliased variable'} | ${{ $CI_REGISTRY: 'registry.com' }} | ${'$CI_REGISTRY/image:1.0'} | ${{ depName: '$CI_REGISTRY/image', packageName: 'registry.com/image', currentValue: '1.0', autoReplaceStringTemplate: `$CI_REGISTRY/image${versionAndDigestTemplate}` }} + ${'variables with brackets'} | ${{ '${CI_REGISTRY}': 'registry.com' }} | ${'${CI_REGISTRY}/image:1.0'} | ${{ depName: '${CI_REGISTRY}/image', packageName: 'registry.com/image', currentValue: '1.0', autoReplaceStringTemplate: `$\{CI_REGISTRY}/image${versionAndDigestTemplate}` }} ${'not aliased variable'} | ${{}} | ${'$CI_REGISTRY/image:1.0'} | ${{ autoReplaceStringTemplate: defaultAutoReplaceStringTemplate }} ${'plain image'} | ${{}} | ${'registry.com/image:1.0'} | ${{ depName: 'registry.com/image', currentValue: '1.0', autoReplaceStringTemplate: defaultAutoReplaceStringTemplate }} `( diff --git a/lib/modules/manager/gradle/__fixtures__/2/libs.versions.toml b/lib/modules/manager/gradle/__fixtures__/2/libs.versions.toml deleted file mode 100644 index 62f98ea855c26c..00000000000000 --- a/lib/modules/manager/gradle/__fixtures__/2/libs.versions.toml +++ /dev/null @@ -1,17 +0,0 @@ -[versions] -kotlin = "1.5.21" -retro_fit = "2.8.2" - -[libraries] -okHttp = "com.squareup.okhttp3:okhttp:4.9.0" -okio = { module = "com.squareup.okio:okio", version = "2.8.0" } -picasso = { group = "com.squareup.picasso", name = "picasso", version = "2.5.1" } -retrofit2-retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retro_fit" } -google-firebase-analytics = { module = "com.google.firebase:firebase-analytics" } -google-firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics" } -google-firebase-messaging = "com.google.firebase:firebase-messaging" - -[plugins] -kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version = "1.5.21" } -kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } -multiJvm = "org.danilopianini.multi-jvm-test-plugin:0.3.0" diff --git a/lib/modules/manager/gradle/__fixtures__/3/libs.versions.toml b/lib/modules/manager/gradle/__fixtures__/3/libs.versions.toml deleted file mode 100644 index 7188d1a6c6b443..00000000000000 --- a/lib/modules/manager/gradle/__fixtures__/3/libs.versions.toml +++ /dev/null @@ -1,10 +0,0 @@ -[versions] -# Releases: http://someWebsite.com/junit/1.4.9 -mocha-junit-reporter = "2.0.2" -# JUnit 1.4.9 is awesome! -junit = "1.4.9" - - -[libraries] -junit-legacy = { module = "junit:junit", version.ref = "junit" } -mocha-junit = { module = "mocha-junit:mocha-junit", version.ref = "mocha.junit.reporter" } diff --git a/lib/modules/manager/gradle/__fixtures__/1/libs.versions.toml b/lib/modules/manager/gradle/__fixtures__/libs.versions.toml similarity index 100% rename from lib/modules/manager/gradle/__fixtures__/1/libs.versions.toml rename to lib/modules/manager/gradle/__fixtures__/libs.versions.toml diff --git a/lib/modules/manager/gradle/__snapshots__/parser.spec.ts.snap b/lib/modules/manager/gradle/__snapshots__/parser.spec.ts.snap deleted file mode 100644 index 571951909582d3..00000000000000 --- a/lib/modules/manager/gradle/__snapshots__/parser.spec.ts.snap +++ /dev/null @@ -1,103 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`modules/manager/gradle/parser calculations parses fixture from "gradle" manager 1`] = ` -[ - { - "currentValue": "1.5.2.RELEASE", - "depName": "org.springframework.boot:spring-boot-gradle-plugin", - "groupName": "springBootVersion", - "managerData": { - "fileReplacePosition": 53, - "packageFile": "build.gradle", - }, - }, - { - "currentValue": "1.2.3", - "depName": "com.github.jengelman.gradle.plugins:shadow", - "managerData": { - "fileReplacePosition": 417, - "packageFile": "build.gradle", - }, - }, - { - "currentValue": "0.1", - "depName": "com.fkorotkov:gradle-libraries-plugin", - "managerData": { - "fileReplacePosition": 481, - "packageFile": "build.gradle", - }, - }, - { - "currentValue": "0.2.3", - "depName": "gradle.plugin.se.patrikerdes:gradle-use-latest-versions-plugin", - "managerData": { - "fileReplacePosition": 568, - "packageFile": "build.gradle", - }, - }, - { - "currentValue": "3.1.1", - "depName": "org.apache.openjpa:openjpa", - "managerData": { - "fileReplacePosition": 621, - "packageFile": "build.gradle", - }, - }, - { - "currentValue": "0.13.0", - "depName": "com.gradle.publish:plugin-publish-plugin", - "managerData": { - "fileReplacePosition": 688, - "packageFile": "build.gradle", - }, - }, - { - "currentValue": "6.0.9.RELEASE", - "depName": "org.grails:gorm-hibernate5-spring-boot", - "managerData": { - "fileReplacePosition": 1882, - "packageFile": "build.gradle", - }, - }, - { - "currentValue": "6.0.5", - "depName": "mysql:mysql-connector-java", - "managerData": { - "fileReplacePosition": 1938, - "packageFile": "build.gradle", - }, - }, - { - "currentValue": "1.0-groovy-2.4", - "depName": "org.spockframework:spock-spring", - "managerData": { - "fileReplacePosition": 1996, - "packageFile": "build.gradle", - }, - }, - { - "currentValue": "1.3", - "depName": "org.hamcrest:hamcrest-core", - "managerData": { - "fileReplacePosition": 2101, - "packageFile": "build.gradle", - }, - }, - { - "currentValue": "3.1", - "depName": "cglib:cglib-nodep", - "managerData": { - "fileReplacePosition": 2189, - "packageFile": "build.gradle", - }, - }, - { - "currentValue": "3.1.1", - "depName": "org.apache.openjpa:openjpa", - "managerData": { - "fileReplacePosition": 2295, - "packageFile": "build.gradle", - }, - }, -] -`; diff --git a/lib/modules/manager/gradle/extract.spec.ts b/lib/modules/manager/gradle/extract.spec.ts index 7a0900ddfc2b3c..25c24846e3805c 100644 --- a/lib/modules/manager/gradle/extract.spec.ts +++ b/lib/modules/manager/gradle/extract.spec.ts @@ -1,4 +1,4 @@ -import { codeBlock, stripIndent } from 'common-tags'; +import { codeBlock } from 'common-tags'; import { Fixtures } from '../../../../test/fixtures'; import { fs, logger, partial } from '../../../../test/util'; import type { ExtractConfig } from '../types'; @@ -188,27 +188,27 @@ describe('modules/manager/gradle/extract', () => { { depName: 'javax.cache:cache-api', currentValue: '1.1.0', - groupName: 'Libraries.jCache', + sharedVariableName: 'Libraries.jCache', }, { depName: 'com.android.tools.build:gradle', currentValue: '4.1.2', - groupName: 'Libraries.Android.Tools.version', + sharedVariableName: 'Libraries.Android.Tools.version', }, { depName: 'androidx.test:core', currentValue: '1.3.0-rc01', - groupName: 'Libraries.Test.version', + sharedVariableName: 'Libraries.Test.version', }, { depName: 'androidx.test.espresso:espresso-core', currentValue: '3.3.0-rc01', - groupName: 'Libraries.Test.Espresso.version', + sharedVariableName: 'Libraries.Test.Espresso.version', }, { depName: 'androidx.test:core-ktx', currentValue: '1.3.0-rc01', - groupName: 'Libraries.Test.version', + sharedVariableName: 'Libraries.Test.version', }, ], }, @@ -218,7 +218,7 @@ describe('modules/manager/gradle/extract', () => { { depName: 'org.jetbrains.kotlin:kotlin-stdlib', currentValue: '1.8.10', - groupName: 'GradleDeps.Kotlin.version', + sharedVariableName: 'GradleDeps.Kotlin.version', }, ], }, @@ -228,12 +228,12 @@ describe('modules/manager/gradle/extract', () => { { depName: 'com.fasterxml.jackson.core:jackson-annotations', currentValue: '2.9.10', - groupName: 'Versions.jackson', + sharedVariableName: 'Versions.jackson', }, { depName: 'io.reactivex.rxjava2:rxjava', currentValue: '1.2.3', - groupName: 'Versions.rxjava', + sharedVariableName: 'Versions.rxjava', }, ], }, @@ -499,7 +499,7 @@ describe('modules/manager/gradle/extract', () => { describe('version catalogs', () => { it('works with dependency catalogs', async () => { const fsMock = { - 'gradle/libs.versions.toml': Fixtures.get('1/libs.versions.toml'), + 'gradle/libs.versions.toml': Fixtures.get('libs.versions.toml'), }; mockFs(fsMock); @@ -513,7 +513,7 @@ describe('modules/manager/gradle/extract', () => { deps: [ { depName: 'io.gitlab.arturbosch.detekt:detekt-formatting', - groupName: 'detekt', + sharedVariableName: 'detekt', currentValue: '1.17.0', managerData: { fileReplacePosition: 21, @@ -522,7 +522,7 @@ describe('modules/manager/gradle/extract', () => { }, { depName: 'io.kotest:kotest-assertions-core-jvm', - groupName: 'kotest', + sharedVariableName: 'kotest', currentValue: '4.6.0', managerData: { fileReplacePosition: 51, @@ -531,7 +531,7 @@ describe('modules/manager/gradle/extract', () => { }, { depName: 'io.kotest:kotest-runner-junit5', - groupName: 'kotest', + sharedVariableName: 'kotest', currentValue: '4.6.0', managerData: { fileReplacePosition: 51, @@ -614,118 +614,6 @@ describe('modules/manager/gradle/extract', () => { ]); }); - it('supports versions declared as single string', async () => { - const fsMock = { - 'gradle/libs.versions.toml': Fixtures.get('2/libs.versions.toml'), - }; - mockFs(fsMock); - - const res = await extractAllPackageFiles( - partial(), - Object.keys(fsMock), - ); - - expect(res).toMatchObject([ - { - packageFile: 'gradle/libs.versions.toml', - deps: [ - { - depName: 'com.squareup.okhttp3:okhttp', - currentValue: '4.9.0', - managerData: { - fileReplacePosition: 100, - packageFile: 'gradle/libs.versions.toml', - }, - }, - { - depName: 'com.squareup.okio:okio', - currentValue: '2.8.0', - managerData: { - fileReplacePosition: 162, - packageFile: 'gradle/libs.versions.toml', - }, - }, - { - depName: 'com.squareup.picasso:picasso', - currentValue: '2.5.1', - managerData: { - fileReplacePosition: 244, - packageFile: 'gradle/libs.versions.toml', - }, - }, - { - depName: 'com.squareup.retrofit2:retrofit', - groupName: 'retro.fit', - currentValue: '2.8.2', - managerData: { - fileReplacePosition: 42, - packageFile: 'gradle/libs.versions.toml', - }, - }, - { - depName: 'google-firebase-analytics', - managerData: { - packageFile: 'gradle/libs.versions.toml', - }, - skipReason: 'unspecified-version', - }, - { - depName: 'google-firebase-crashlytics', - managerData: { - packageFile: 'gradle/libs.versions.toml', - }, - skipReason: 'unspecified-version', - }, - { - depName: 'google-firebase-messaging', - managerData: { - packageFile: 'gradle/libs.versions.toml', - }, - skipReason: 'unspecified-version', - }, - { - depName: 'org.jetbrains.kotlin.jvm', - depType: 'plugin', - currentValue: '1.5.21', - commitMessageTopic: 'plugin kotlinJvm', - packageName: - 'org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin', - managerData: { - fileReplacePosition: 663, - packageFile: 'gradle/libs.versions.toml', - }, - registryUrls: ['https://plugins.gradle.org/m2/'], - }, - { - depName: 'org.jetbrains.kotlin.plugin.serialization', - depType: 'plugin', - currentValue: '1.5.21', - packageName: - 'org.jetbrains.kotlin.plugin.serialization:org.jetbrains.kotlin.plugin.serialization.gradle.plugin', - managerData: { - fileReplacePosition: 21, - packageFile: 'gradle/libs.versions.toml', - }, - registryUrls: ['https://plugins.gradle.org/m2/'], - }, - { - depName: 'org.danilopianini.multi-jvm-test-plugin', - depType: 'plugin', - currentValue: '0.3.0', - commitMessageTopic: 'plugin multiJvm', - packageName: - 'org.danilopianini.multi-jvm-test-plugin:org.danilopianini.multi-jvm-test-plugin.gradle.plugin', - managerData: { - fileReplacePosition: 824, - packageFile: 'gradle/libs.versions.toml', - }, - registryUrls: ['https://plugins.gradle.org/m2/'], - }, - ], - }, - ]); - }); - it('ignores empty TOML file', async () => { const fsMock = { 'gradle/libs.versions.toml': '', @@ -742,7 +630,7 @@ describe('modules/manager/gradle/extract', () => { it('deletes commit message for plugins with version reference', async () => { const fsMock = { - 'gradle/libs.versions.toml': stripIndent` + 'gradle/libs.versions.toml': codeBlock` [versions] detekt = "1.18.1" @@ -765,7 +653,7 @@ describe('modules/manager/gradle/extract', () => { deps: [ { depName: 'io.gitlab.arturbosch.detekt:detekt-formatting', - groupName: 'detekt', + sharedVariableName: 'detekt', currentValue: '1.18.1', managerData: { fileReplacePosition: 21, @@ -784,52 +672,13 @@ describe('modules/manager/gradle/extract', () => { fileReplacePosition: 21, packageFile: 'gradle/libs.versions.toml', }, - groupName: 'detekt', + sharedVariableName: 'detekt', fileReplacePosition: 21, }, ], }, ]); }); - - it('changes the dependency version, not the comment version', async () => { - const fsMock = { - 'gradle/libs.versions.toml': Fixtures.get('3/libs.versions.toml'), - }; - mockFs(fsMock); - - const res = await extractAllPackageFiles( - partial(), - Object.keys(fsMock), - ); - expect(res).toMatchObject([ - { - packageFile: 'gradle/libs.versions.toml', - deps: [ - { - depName: 'junit:junit', - groupName: 'junit', - currentValue: '1.4.9', - managerData: { - fileReplacePosition: 124, - packageFile: 'gradle/libs.versions.toml', - }, - fileReplacePosition: 124, - }, - { - depName: 'mocha-junit:mocha-junit', - groupName: 'mocha.junit.reporter', - currentValue: '2.0.2', - managerData: { - fileReplacePosition: 82, - packageFile: 'gradle/libs.versions.toml', - }, - fileReplacePosition: 82, - }, - ], - }, - ]); - }); }); describe('apply from', () => { @@ -996,7 +845,7 @@ describe('modules/manager/gradle/extract', () => { it('parses versions files', async () => { const fsMock = { 'versions.props': `org.apache.lucene:* = 1.2.3`, - 'versions.lock': stripIndent` + 'versions.lock': codeBlock` # Run ./gradlew --write-locks to regenerate this file org.apache.lucene:lucene-core:1.2.3 (10 constraints: 95be0c15) org.apache.lucene:lucene-codecs:1.2.3 (5 constraints: 1231231) @@ -1020,7 +869,7 @@ describe('modules/manager/gradle/extract', () => { depName: 'org.apache.lucene:lucene-core', depType: 'dependencies', fileReplacePosition: 22, - groupName: 'org.apache.lucene:*', + sharedVariableName: 'org.apache.lucene:*', lockedVersion: '1.2.3', managerData: { fileReplacePosition: 22, @@ -1031,7 +880,7 @@ describe('modules/manager/gradle/extract', () => { depName: 'org.apache.lucene:lucene-codecs', depType: 'dependencies', fileReplacePosition: 22, - groupName: 'org.apache.lucene:*', + sharedVariableName: 'org.apache.lucene:*', lockedVersion: '1.2.3', managerData: { fileReplacePosition: 22, @@ -1046,7 +895,7 @@ describe('modules/manager/gradle/extract', () => { it('plugin not used due to lockfile not a GCV lockfile', async () => { const fsMock = { 'versions.props': `org.apache.lucene:* = 1.2.3`, - 'versions.lock': stripIndent` + 'versions.lock': codeBlock` This is NOT a lock file `, }; @@ -1075,14 +924,14 @@ describe('modules/manager/gradle/extract', () => { it('supports multiple levels of glob', async () => { const fsMock = { - 'versions.props': stripIndent` + 'versions.props': codeBlock` org.apache.* = 4 org.apache.lucene:* = 3 org.apache.lucene:a.* = 2 org.apache.lucene:a.b = 1 org.apache.foo*:* = 5 `, - 'versions.lock': stripIndent` + 'versions.lock': codeBlock` # Run ./gradlew --write-locks to regenerate this file org.apache.solr:x.y:1 (10 constraints: 95be0c15) org.apache.lucene:a.b:1 (10 constraints: 95be0c15) @@ -1128,7 +977,7 @@ describe('modules/manager/gradle/extract', () => { depName: 'org.apache.lucene:a.c', currentValue: '2', lockedVersion: '1', - groupName: 'org.apache.lucene:a.*', + sharedVariableName: 'org.apache.lucene:a.*', fileReplacePosition: 65, depType: 'dependencies', }, @@ -1140,7 +989,7 @@ describe('modules/manager/gradle/extract', () => { depName: 'org.apache.lucene:a.d', currentValue: '2', lockedVersion: '1', - groupName: 'org.apache.lucene:a.*', + sharedVariableName: 'org.apache.lucene:a.*', fileReplacePosition: 65, depType: 'dependencies', }, @@ -1152,7 +1001,7 @@ describe('modules/manager/gradle/extract', () => { depName: 'org.apache.lucene:d', currentValue: '3', lockedVersion: '1', - groupName: 'org.apache.lucene:*', + sharedVariableName: 'org.apache.lucene:*', fileReplacePosition: 39, depType: 'dependencies', }, @@ -1164,7 +1013,7 @@ describe('modules/manager/gradle/extract', () => { depName: 'org.apache.lucene:e.f', currentValue: '3', lockedVersion: '1', - groupName: 'org.apache.lucene:*', + sharedVariableName: 'org.apache.lucene:*', fileReplacePosition: 39, depType: 'dependencies', }, @@ -1176,7 +1025,7 @@ describe('modules/manager/gradle/extract', () => { depName: 'org.apache.foo-bar:a', currentValue: '5', lockedVersion: '1', - groupName: 'org.apache.foo*:*', + sharedVariableName: 'org.apache.foo*:*', fileReplacePosition: 113, depType: 'dependencies', }, diff --git a/lib/modules/manager/gradle/extract/catalog.spec.ts b/lib/modules/manager/gradle/extract/catalog.spec.ts new file mode 100644 index 00000000000000..6f470a04b77c07 --- /dev/null +++ b/lib/modules/manager/gradle/extract/catalog.spec.ts @@ -0,0 +1,194 @@ +import { codeBlock } from 'common-tags'; +import { parseCatalog } from './catalog'; + +describe('modules/manager/gradle/extract/catalog', () => { + it('supports versions declared as single string', () => { + const input = codeBlock` + [versions] + kotlin = "1.5.21" + retro_fit = "2.8.2" + + [libraries] + okHttp = "com.squareup.okhttp3:okhttp:4.9.0" + okio = { module = "com.squareup.okio:okio", version = "2.8.0" } + picasso = { group = "com.squareup.picasso", name = "picasso", version = "2.5.1" } + retrofit2-retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retro_fit" } + google-firebase-analytics = { module = "com.google.firebase:firebase-analytics" } + google-firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics" } + google-firebase-messaging = "com.google.firebase:firebase-messaging" + + [plugins] + kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version = "1.5.21" } + kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } + multiJvm = "org.danilopianini.multi-jvm-test-plugin:0.3.0" + `; + const res = parseCatalog('gradle/libs.versions.toml', input); + expect(res).toStrictEqual([ + { + depName: 'com.squareup.okhttp3:okhttp', + currentValue: '4.9.0', + managerData: { + fileReplacePosition: 100, + packageFile: 'gradle/libs.versions.toml', + }, + }, + { + depName: 'com.squareup.okio:okio', + currentValue: '2.8.0', + managerData: { + fileReplacePosition: 162, + packageFile: 'gradle/libs.versions.toml', + }, + }, + { + depName: 'com.squareup.picasso:picasso', + currentValue: '2.5.1', + managerData: { + fileReplacePosition: 244, + packageFile: 'gradle/libs.versions.toml', + }, + }, + { + depName: 'com.squareup.retrofit2:retrofit', + sharedVariableName: 'retro.fit', + currentValue: '2.8.2', + managerData: { + fileReplacePosition: 42, + packageFile: 'gradle/libs.versions.toml', + }, + }, + { + depName: 'google-firebase-analytics', + managerData: { + packageFile: 'gradle/libs.versions.toml', + }, + skipReason: 'unspecified-version', + }, + { + depName: 'google-firebase-crashlytics', + managerData: { + packageFile: 'gradle/libs.versions.toml', + }, + skipReason: 'unspecified-version', + }, + { + depName: 'google-firebase-messaging', + managerData: { + packageFile: 'gradle/libs.versions.toml', + }, + skipReason: 'unspecified-version', + }, + { + depName: 'org.jetbrains.kotlin.jvm', + depType: 'plugin', + currentValue: '1.5.21', + commitMessageTopic: 'plugin kotlinJvm', + packageName: + 'org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin', + managerData: { + fileReplacePosition: 663, + packageFile: 'gradle/libs.versions.toml', + }, + }, + { + depName: 'org.jetbrains.kotlin.plugin.serialization', + depType: 'plugin', + currentValue: '1.5.21', + sharedVariableName: 'kotlin', + packageName: + 'org.jetbrains.kotlin.plugin.serialization:org.jetbrains.kotlin.plugin.serialization.gradle.plugin', + managerData: { + fileReplacePosition: 21, + packageFile: 'gradle/libs.versions.toml', + }, + }, + { + depName: 'org.danilopianini.multi-jvm-test-plugin', + depType: 'plugin', + currentValue: '0.3.0', + commitMessageTopic: 'plugin multiJvm', + packageName: + 'org.danilopianini.multi-jvm-test-plugin:org.danilopianini.multi-jvm-test-plugin.gradle.plugin', + managerData: { + fileReplacePosition: 824, + packageFile: 'gradle/libs.versions.toml', + }, + }, + ]); + }); + + it('deletes commit message for plugins with version reference', () => { + const input = codeBlock` + [versions] + detekt = "1.18.1" + + [plugins] + detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } + + [libraries] + detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" } + `; + const res = parseCatalog('gradle/libs.versions.toml', input); + + expect(res).toStrictEqual([ + { + depName: 'io.gitlab.arturbosch.detekt:detekt-formatting', + sharedVariableName: 'detekt', + currentValue: '1.18.1', + managerData: { + fileReplacePosition: 21, + packageFile: 'gradle/libs.versions.toml', + }, + }, + { + depType: 'plugin', + depName: 'io.gitlab.arturbosch.detekt', + packageName: + 'io.gitlab.arturbosch.detekt:io.gitlab.arturbosch.detekt.gradle.plugin', + currentValue: '1.18.1', + managerData: { + fileReplacePosition: 21, + packageFile: 'gradle/libs.versions.toml', + }, + sharedVariableName: 'detekt', + }, + ]); + }); + + it('changes the dependency version, not the comment version', () => { + const input = codeBlock` + [versions] + # Releases: http://someWebsite.com/junit/1.4.9 + mocha-junit-reporter = "2.0.2" + # JUnit 1.4.9 is awesome! + junit = "1.4.9" + + + [libraries] + junit-legacy = { module = "junit:junit", version.ref = "junit" } + mocha-junit = { module = "mocha-junit:mocha-junit", version.ref = "mocha.junit.reporter" } + `; + const res = parseCatalog('gradle/libs.versions.toml', input); + + expect(res).toStrictEqual([ + { + depName: 'junit:junit', + sharedVariableName: 'junit', + currentValue: '1.4.9', + managerData: { + fileReplacePosition: 124, + packageFile: 'gradle/libs.versions.toml', + }, + }, + { + depName: 'mocha-junit:mocha-junit', + sharedVariableName: 'mocha.junit.reporter', + currentValue: '2.0.2', + managerData: { + fileReplacePosition: 82, + packageFile: 'gradle/libs.versions.toml', + }, + }, + ]); + }); +}); diff --git a/lib/modules/manager/gradle/extract/catalog.ts b/lib/modules/manager/gradle/extract/catalog.ts index e4ed8d634266a6..36a21a5b80639e 100644 --- a/lib/modules/manager/gradle/extract/catalog.ts +++ b/lib/modules/manager/gradle/extract/catalog.ts @@ -188,7 +188,7 @@ function extractDependency({ versionSubContent: string; }): PackageDependency { if (is.string(descriptor)) { - const [groupName, name, currentValue] = descriptor.split(':'); + const [group, name, currentValue] = descriptor.split(':'); if (!currentValue) { return { depName, @@ -196,7 +196,7 @@ function extractDependency({ }; } return { - depName: `${groupName}:${name}`, + depName: `${group}:${name}`, currentValue, managerData: { fileReplacePosition: @@ -236,7 +236,7 @@ function extractDependency({ } if (isVersionPointer(descriptor.version)) { - dependency.groupName = normalizeAlias(descriptor.version.ref); + dependency.sharedVariableName = normalizeAlias(descriptor.version.ref); } return dependency; @@ -298,7 +298,7 @@ export function parseCatalog( dependency.skipReason = skipReason; } if (isVersionPointer(version) && dependency.commitMessageTopic) { - dependency.groupName = normalizeAlias(version.ref); + dependency.sharedVariableName = normalizeAlias(version.ref); delete dependency.commitMessageTopic; } 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 5ebaef86501b14..850031576ba663 100644 --- a/lib/modules/manager/gradle/extract/consistent-versions-plugin.spec.ts +++ b/lib/modules/manager/gradle/extract/consistent-versions-plugin.spec.ts @@ -1,16 +1,17 @@ -import { stripIndent } from 'common-tags'; +import { codeBlock } from 'common-tags'; import { + parseGcv, parseLockFile, parsePropsFile, usesGcv, } from './consistent-versions-plugin'; describe('modules/manager/gradle/extract/consistent-versions-plugin', () => { - it('gradle-consistent-versions plugin works for sub folders', () => { + it('works for sub folders', () => { const fsMock = { 'mysub/build.gradle.kts': `(this file contains) 'com.palantir.consistent-versions'`, 'mysub/versions.props': `org.apache.lucene:* = 1.2.3`, - 'mysub/versions.lock': stripIndent` + 'mysub/versions.lock': codeBlock` # Run ./gradlew --write-locks to regenerate this file org.apache.lucene:lucene-core:1.2.3`, 'othersub/build.gradle.kts': `nothing here`, @@ -24,7 +25,7 @@ describe('modules/manager/gradle/extract/consistent-versions-plugin', () => { const fsMock = { 'build.gradle.kts': `(this file contains) 'com.palantir.consistent-versions'`, 'versions.props': `org.apache.lucene:* = 1.2.3`, - 'versions.lock': stripIndent` + 'versions.lock': codeBlock` # Run ./gradlew writeVersionsLock to regenerate this file org.apache.lucene:lucene-core:1.2.3`, }; @@ -36,7 +37,7 @@ describe('modules/manager/gradle/extract/consistent-versions-plugin', () => { const fsMock = { 'build.gradle.kts': `(this file contains) 'com.palantir.consistent-versions'`, 'versions.props': `org.apache.lucene:* = 1.2.3`, - 'versions.lock': stripIndent` + 'versions.lock': codeBlock` # Run ./gradlew writeVersionsLocks to regenerate this file org.apache.lucene:lucene-core:1.2.3`, }; @@ -44,7 +45,7 @@ describe('modules/manager/gradle/extract/consistent-versions-plugin', () => { expect(usesGcv('versions.props', fsMock)).toBeTrue(); }); - it('gradle-consistent-versions plugin correct position for CRLF and LF', () => { + it('correct position for CRLF and LF', () => { const crlfProps = parsePropsFile(`a.b:c.d=1\r\na.b:c.e=2`); expect(crlfProps).toBeArrayOfSize(2); expect(crlfProps[0].has('a.b:c.e')).toBeTrue(); @@ -56,8 +57,8 @@ describe('modules/manager/gradle/extract/consistent-versions-plugin', () => { expect(lfProps[0].get('a.b:c.e')).toMatchObject({ filePos: 18 }); }); - it('gradle-consistent-versions plugin test bogus input lines', () => { - const parsedProps = parsePropsFile(stripIndent` + it('test bogus input lines', () => { + const parsedProps = parsePropsFile(codeBlock` # comment:foo.bar = 1 123.foo:bar = 2 this has:spaces = 3 @@ -71,7 +72,7 @@ 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 - const parsedLock = parseLockFile(stripIndent` + const parsedLock = parseLockFile(codeBlock` # comment:foo.bar:1 (10 constraints: 95be0c15) 123.foo:bar:2 (10 constraints: 95be0c15) this has:spaces:3 (10 constraints: 95be0c15) @@ -92,4 +93,96 @@ describe('modules/manager/gradle/extract/consistent-versions-plugin', () => { depType: 'test', }); }); + + it('supports multiple levels of glob', () => { + const fsMock = { + 'versions.props': codeBlock` + org.apache.* = 4 + org.apache.lucene:* = 3 + org.apache.lucene:a.* = 2 + org.apache.lucene:a.b = 1 + org.apache.foo*:* = 5 + `, + 'versions.lock': codeBlock` + # Run ./gradlew --write-locks to regenerate this file + org.apache.solr:x.y:1 (10 constraints: 95be0c15) + org.apache.lucene:a.b:1 (10 constraints: 95be0c15) + org.apache.lucene:a.c:1 (10 constraints: 95be0c15) + org.apache.lucene:a.d:1 (10 constraints: 95be0c15) + org.apache.lucene:d:1 (10 constraints: 95be0c15) + org.apache.lucene:e.f:1 (10 constraints: 95be0c15) + org.apache.foo-bar:a:1 (10 constraints: 95be0c15) + `, + }; + const res = parseGcv('versions.props', fsMock); + + // Each lock dep is only present once, with highest prio for exact prop match, then globs from longest to shortest + expect(res).toStrictEqual([ + { + managerData: { + packageFile: 'versions.props', + fileReplacePosition: 91, + }, + depName: 'org.apache.lucene:a.b', + currentValue: '1', + lockedVersion: '1', + depType: 'dependencies', + }, + { + managerData: { + packageFile: 'versions.props', + fileReplacePosition: 65, + }, + depName: 'org.apache.lucene:a.c', + currentValue: '2', + lockedVersion: '1', + sharedVariableName: 'org.apache.lucene:a.*', + depType: 'dependencies', + }, + { + managerData: { + packageFile: 'versions.props', + fileReplacePosition: 65, + }, + depName: 'org.apache.lucene:a.d', + currentValue: '2', + lockedVersion: '1', + sharedVariableName: 'org.apache.lucene:a.*', + depType: 'dependencies', + }, + { + managerData: { + packageFile: 'versions.props', + fileReplacePosition: 39, + }, + depName: 'org.apache.lucene:d', + currentValue: '3', + lockedVersion: '1', + sharedVariableName: 'org.apache.lucene:*', + depType: 'dependencies', + }, + { + managerData: { + packageFile: 'versions.props', + fileReplacePosition: 39, + }, + depName: 'org.apache.lucene:e.f', + currentValue: '3', + lockedVersion: '1', + sharedVariableName: 'org.apache.lucene:*', + depType: 'dependencies', + }, + { + managerData: { + fileReplacePosition: 113, + packageFile: 'versions.props', + }, + depName: 'org.apache.foo-bar:a', + currentValue: '5', + lockedVersion: '1', + sharedVariableName: 'org.apache.foo*:*', + depType: 'dependencies', + }, + ]); + }); }); diff --git a/lib/modules/manager/gradle/extract/consistent-versions-plugin.ts b/lib/modules/manager/gradle/extract/consistent-versions-plugin.ts index 4feeb371208802..22ec356576adbe 100644 --- a/lib/modules/manager/gradle/extract/consistent-versions-plugin.ts +++ b/lib/modules/manager/gradle/extract/consistent-versions-plugin.ts @@ -102,7 +102,7 @@ export function parseGcv( currentValue: propVerAndPos.version, lockedVersion: lockVersionAndDepType.version, depType: lockVersionAndDepType.depType, - groupName: propDepGlob, + sharedVariableName: propDepGlob, } satisfies PackageDependency; extractedDeps.push(newDep); // Remove from the lockfile map so the same lib will not be included in more generic globs later diff --git a/lib/modules/manager/gradle/parser.spec.ts b/lib/modules/manager/gradle/parser.spec.ts index d234b041ed7504..faa099f3e4345b 100644 --- a/lib/modules/manager/gradle/parser.spec.ts +++ b/lib/modules/manager/gradle/parser.spec.ts @@ -8,7 +8,11 @@ import { parseKotlinSource, parseProps, } from './parser'; -import { GRADLE_PLUGINS, REGISTRY_URLS } from './parser/common'; +import { + GRADLE_PLUGINS, + GRADLE_TEST_SUITES, + REGISTRY_URLS, +} from './parser/common'; jest.mock('../../../util/fs'); @@ -179,32 +183,32 @@ describe('modules/manager/gradle/parser', () => { expect(deps).toMatchObject([ { depName: 'org.slf4j:jcl-over-slf4j', - groupName: 'slfj4Version', + sharedVariableName: 'slfj4Version', currentValue: '2.0.0', }, { depName: 'org.jetbrains.kotlinx:kotlinx-coroutines-core', - groupName: 'libraries.releaseCoroutines', + sharedVariableName: 'libraries.releaseCoroutines', currentValue: '0.26.1-eap13', }, { depName: 'org.slf4j:slf4j-api', - groupName: 'slfj4Version', + sharedVariableName: 'slfj4Version', currentValue: '2.0.0', }, { depName: 'androidx.lifecycle:lifecycle-runtime-ktx', - groupName: 'lifecycle_version', + sharedVariableName: 'lifecycle_version', currentValue: '2.5.1', }, { depName: 'androidx.lifecycle:lifecycle-viewmodel-ktx', - groupName: 'lifecycle_version', + sharedVariableName: 'lifecycle_version', currentValue: '2.5.1', }, { depName: 'org.slf4j:slf4j-ext', - groupName: 'slfj4Version', + sharedVariableName: 'slfj4Version', currentValue: '2.0.0', }, ]); @@ -333,17 +337,17 @@ describe('modules/manager/gradle/parser', () => { expect(deps).toMatchObject([ { depName: 'org.slf4j:jcl-over-slf4j', - groupName: 'slfj4Version', + sharedVariableName: 'slfj4Version', currentValue: '2.0.0', }, { depName: 'org.jetbrains.kotlinx:kotlinx-coroutines-core', - groupName: 'libraries.releaseCoroutines', + sharedVariableName: 'libraries.releaseCoroutines', currentValue: '0.26.1-eap13', }, { depName: 'org.slf4j:slf4j-api', - groupName: 'slfj4Version', + sharedVariableName: 'slfj4Version', currentValue: '2.0.0', }, ]); @@ -372,18 +376,18 @@ describe('modules/manager/gradle/parser', () => { ${'foo = "1.2.3"'} | ${'"foo:bar:$foo@@@"'} | ${null} ${''} | ${'"foo:bar:$baz"'} | ${null} ${'foo = "1"; bar = "2"; baz = "3"'} | ${'"foo:bar:$foo.$bar.$baz"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', skipReason: 'contains-variable' }} - ${'baz = "1.2.3"'} | ${'"foo:bar:$baz"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', groupName: 'baz' }} - ${'foo.bar = "1.2.3"'} | ${'"foo:bar:$foo.bar"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', groupName: 'foo.bar' }} + ${'baz = "1.2.3"'} | ${'"foo:bar:$baz"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'baz' }} + ${'foo.bar = "1.2.3"'} | ${'"foo:bar:$foo.bar"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'foo.bar' }} ${'foo = "1.2.3"'} | ${'"foo:bar_$foo:4.5.6"'} | ${{ depName: 'foo:bar_1.2.3', managerData: { fileReplacePosition: 28 } }} ${'foo = "bar"'} | ${'"foo:${foo}1:1"'} | ${{ depName: 'foo:bar1', currentValue: '1', managerData: { fileReplacePosition: 25 } }} ${'bar = "bar:1.2.3"'} | ${'"foo:$bar"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', skipReason: 'contains-variable' }} - ${'baz = "1.2.3"'} | ${'foobar = "foo:bar:$baz"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', groupName: 'baz' }} + ${'baz = "1.2.3"'} | ${'foobar = "foo:bar:$baz"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'baz' }} ${'foo = "${bar}"; baz = "1.2.3"'} | ${'"foo:bar:${baz}"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3' }} - ${'baz = "1.2.3"'} | ${'"foo:bar:${ext[\'baz\']}"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', groupName: 'baz' }} - ${'baz = "1.2.3"'} | ${'"foo:bar:${ext.baz}"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', groupName: 'baz' }} - ${'baz = "1.2.3"'} | ${'"foo:bar:${project.ext[\'baz\']}"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', groupName: 'baz' }} - ${'a = "foo"; b = "bar"; c="1.2.3"'} | ${'"${a}:${b}:${property("c")}"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', groupName: 'c' }} - ${'a = "foo"; b = "bar"; c="1.2.3"'} | ${'"${a}:${b}:${properties["c"]}"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', groupName: 'c' }} + ${'baz = "1.2.3"'} | ${'"foo:bar:${ext[\'baz\']}"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'baz' }} + ${'baz = "1.2.3"'} | ${'"foo:bar:${ext.baz}"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'baz' }} + ${'baz = "1.2.3"'} | ${'"foo:bar:${project.ext[\'baz\']}"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'baz' }} + ${'a = "foo"; b = "bar"; c="1.2.3"'} | ${'"${a}:${b}:${property("c")}"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'c' }} + ${'a = "foo"; b = "bar"; c="1.2.3"'} | ${'"${a}:${b}:${properties["c"]}"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'c' }} `('$def | $str', ({ def, str, output }) => { const { deps } = parseGradle([def, str].join('\n')); expect(deps).toMatchObject([output].filter(is.truthy)); @@ -434,7 +438,11 @@ describe('modules/manager/gradle/parser', () => { `; const { deps } = parseGradle(input); expect(deps).toMatchObject([ - { depName: 'foo:bar', currentValue: '1.2.3', groupName: 'baz' }, + { + depName: 'foo:bar', + currentValue: '1.2.3', + sharedVariableName: 'baz', + }, ]); }); }); @@ -452,7 +460,7 @@ describe('modules/manager/gradle/parser', () => { ${''} | ${'kotlin("foo", version = "1.2.3")'} | ${output} ${'some = "foo"'} | ${'kotlin(some, version = "1.2.3")'} | ${output} ${'some = "foo"'} | ${'kotlin("${some}", "1.2.3")'} | ${output} - ${'baz = "1.2.3"'} | ${'kotlin("foo", baz)'} | ${{ ...output, groupName: 'baz' }} + ${'baz = "1.2.3"'} | ${'kotlin("foo", baz)'} | ${{ ...output, sharedVariableName: 'baz' }} ${'baz = "1.2.3"'} | ${'kotlin("foo", version = baz)'} | ${output} ${'baz = "1.2.3"'} | ${'kotlin("foo", property("baz"))'} | ${output} ${'baz = "1.2.3"'} | ${'kotlin("foo", "${baz}456")'} | ${{ skipReason: 'unspecified-version' }} @@ -473,21 +481,21 @@ describe('modules/manager/gradle/parser', () => { ${''} | ${'group: "foo", name: "bar", version: "1.2.3"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3' }} ${''} | ${'group: "foo", name: "bar", version: baz'} | ${null} ${''} | ${'group: "foo", name: "bar", version: "1.2.3@@@"'} | ${null} - ${'baz = "1.2.3"'} | ${'group: "foo", name: "bar", version: baz'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', groupName: 'baz' }} + ${'baz = "1.2.3"'} | ${'group: "foo", name: "bar", version: baz'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'baz' }} ${'some = "foo"'} | ${'group: property("some"), name: property("some"), version: "1.2.3"'} | ${{ depName: 'foo:foo', currentValue: '1.2.3' }} ${'some = "foo"'} | ${'group: some, name: some, version: "1.2.3"'} | ${{ depName: 'foo:foo', currentValue: '1.2.3' }} ${'some = "foo"'} | ${'group: "${some}", name: "${some}", version: "1.2.3"'} | ${{ depName: 'foo:foo', currentValue: '1.2.3' }} - ${'baz = "1.2.3"'} | ${'group: "foo", name: "bar", version: "${baz}"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', groupName: 'baz' }} + ${'baz = "1.2.3"'} | ${'group: "foo", name: "bar", version: "${baz}"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'baz' }} ${'baz = "1.2.3"'} | ${'group: "foo", name: "bar", version: "${baz}456"'} | ${{ depName: 'foo:bar', skipReason: 'unspecified-version' }} ${''} | ${'(group: "foo", name: "bar", version: "1.2.3", classifier: "sources")'} | ${{ depName: 'foo:bar', currentValue: '1.2.3' }} ${''} | ${'(group: "foo", name: "bar", version: "1.2.3") {exclude module: "spring-jcl"}'} | ${{ depName: 'foo:bar', currentValue: '1.2.3' }} ${''} | ${"implementation platform(group: 'foo', name: 'bar', version: '1.2.3')"} | ${{ depName: 'foo:bar', currentValue: '1.2.3' }} ${''} | ${'(group = "foo", name = "bar", version = "1.2.3")'} | ${{ depName: 'foo:bar', currentValue: '1.2.3' }} - ${'baz = "1.2.3"'} | ${'(group = "foo", name = "bar", version = baz)'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', groupName: 'baz' }} + ${'baz = "1.2.3"'} | ${'(group = "foo", name = "bar", version = baz)'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'baz' }} ${'some = "foo"'} | ${'(group = some, name = some, version = "1.2.3")'} | ${{ depName: 'foo:foo', currentValue: '1.2.3' }} ${'some = "foo"'} | ${'(group = "${some}", name = "${some}", version = "1.2.3")'} | ${{ depName: 'foo:foo', currentValue: '1.2.3' }} ${'some = "foo"'} | ${'(group = "${some}" + some, name = some + "bar" + some, version = "1.2.3")'} | ${{ depName: 'foofoo:foobarfoo', currentValue: '1.2.3' }} - ${'baz = "1.2.3"'} | ${'(group = "foo", name = "bar", version = "${baz}")'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', groupName: 'baz' }} + ${'baz = "1.2.3"'} | ${'(group = "foo", name = "bar", version = "${baz}")'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'baz' }} ${'baz = "1.2.3"'} | ${'(group = "foo", name = "bar", version = "${baz}456")'} | ${{ depName: 'foo:bar', currentValue: '1.2.3456', skipReason: 'unspecified-version' }} ${'baz = "1.2.3"'} | ${'(group = "foo", name = "bar", version = baz + "456")'} | ${{ depName: 'foo:bar', currentValue: '1.2.3456', skipReason: 'unspecified-version' }} ${''} | ${'(group = "foo", name = "bar", version = "1.2.3", changing: true)'} | ${{ depName: 'foo:bar', currentValue: '1.2.3' }} @@ -515,17 +523,17 @@ describe('modules/manager/gradle/parser', () => { { depName: 'org.apache.activemq:activemq-broker', currentValue: '5.8.0', - groupName: 'activemq_version', + sharedVariableName: 'activemq_version', }, { depName: 'org.apache.activemq:activemq-kahadb-store', currentValue: '5.8.0', - groupName: 'activemq_version', + sharedVariableName: 'activemq_version', }, { depName: 'org.apache.activemq:activemq-stomp', currentValue: '5.8.0', - groupName: 'activemq_version', + sharedVariableName: 'activemq_version', }, ]); }); @@ -535,16 +543,16 @@ describe('modules/manager/gradle/parser', () => { { depName: 'foo:bar1', currentValue: '1.2.3', - groupName: 'foo:1.2.3', + sharedVariableName: 'foo:1.2.3', }, { depName: 'foo:bar2', currentValue: '1.2.3', - groupName: 'foo:1.2.3', + sharedVariableName: 'foo:1.2.3', }, ]; const validOutput1 = validOutput.map((dep) => { - return { ...dep, groupName: 'baz' }; + return { ...dep, sharedVariableName: 'baz' }; }); it.each` @@ -584,7 +592,7 @@ describe('modules/manager/gradle/parser', () => { ${''} | ${'id("foo.bar") version("1.2.3")'} | ${{ depName: 'foo.bar', packageName: 'foo.bar:foo.bar.gradle.plugin', currentValue: '1.2.3' }} ${''} | ${'id("foo.bar") version "1.2.3"'} | ${{ depName: 'foo.bar', packageName: 'foo.bar:foo.bar.gradle.plugin', currentValue: '1.2.3' }} ${''} | ${'id "foo.bar" version "$baz"'} | ${{ depName: 'foo.bar', skipReason: 'unspecified-version', currentValue: 'baz' }} - ${'baz = "1.2.3"'} | ${'id "foo.bar" version "$baz"'} | ${{ depName: 'foo.bar', packageName: 'foo.bar:foo.bar.gradle.plugin', currentValue: '1.2.3', groupName: 'baz' }} + ${'baz = "1.2.3"'} | ${'id "foo.bar" version "$baz"'} | ${{ depName: 'foo.bar', packageName: 'foo.bar:foo.bar.gradle.plugin', currentValue: '1.2.3', sharedVariableName: 'baz' }} ${'baz = "1.2.3"'} | ${'id("foo.bar") version "$baz"'} | ${{ depName: 'foo.bar', packageName: 'foo.bar:foo.bar.gradle.plugin', currentValue: '1.2.3' }} ${''} | ${'id "foo.bar" version "x${ab}cd"'} | ${{ depName: 'foo.bar', skipReason: 'unspecified-version' }} ${''} | ${'id("foo.bar") version "$baz"'} | ${{ depName: 'foo.bar', skipReason: 'unspecified-version', currentValue: 'baz' }} @@ -597,7 +605,7 @@ describe('modules/manager/gradle/parser', () => { ${'baz = "1.2.3"'} | ${'id("foo.bar") version baz'} | ${{ depName: 'foo.bar', packageName: 'foo.bar:foo.bar.gradle.plugin', currentValue: '1.2.3' }} ${'baz = "1.2.3"'} | ${'id("foo.bar").version(baz)'} | ${{ depName: 'foo.bar', packageName: 'foo.bar:foo.bar.gradle.plugin', currentValue: '1.2.3' }} ${''} | ${'kotlin("jvm") version "1.3.71"'} | ${{ depName: 'org.jetbrains.kotlin.jvm', packageName: 'org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin', currentValue: '1.3.71' }} - ${'baz = "1.3.71"'} | ${'kotlin("jvm") version baz'} | ${{ depName: 'org.jetbrains.kotlin.jvm', packageName: 'org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin', currentValue: '1.3.71', groupName: 'baz' }} + ${'baz = "1.3.71"'} | ${'kotlin("jvm") version baz'} | ${{ depName: 'org.jetbrains.kotlin.jvm', packageName: 'org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin', currentValue: '1.3.71', sharedVariableName: 'baz' }} `('$def | $input', ({ def, input, output }) => { const { deps } = parseGradle([def, input].join('\n')); expect(deps).toMatchObject([output].filter(is.truthy)); @@ -735,7 +743,7 @@ describe('modules/manager/gradle/parser', () => { ${'f = "foo"; b = "bar"'} | ${'library("foo.bar", "${f}", "${b}").version("1.2.3")'} | ${{ depName: 'foo:bar', currentValue: '1.2.3' }} ${'f = "foo"; b = "bar"; v = "1.2.3"'} | ${'library("foo.bar", property("f"), "${b}").version(v)'} | ${{ depName: 'foo:bar', currentValue: '1.2.3' }} ${'f = "foo"; b = "bar"'} | ${'library("foo.bar", "${f}" + f, "${b}").version("1.2.3")'} | ${{ depName: 'foofoo:bar', currentValue: '1.2.3' }} - ${'version("baz", "1.2.3")'} | ${'library("foo.bar", "foo", "bar").versionRef("baz")'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', groupName: 'baz' }} + ${'version("baz", "1.2.3")'} | ${'library("foo.bar", "foo", "bar").versionRef("baz")'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', sharedVariableName: 'baz' }} ${'library("foo-bar_baz-qux", "foo", "bar")'} | ${'"${libs.foo.bar.baz.qux}:1.2.3"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3' }} ${''} | ${'library(["foo.bar", "foo", "bar"]).version("1.2.3")'} | ${null} ${''} | ${'library("foo", "bar", "baz", "qux").version("1.2.3")'} | ${null} @@ -770,6 +778,17 @@ describe('modules/manager/gradle/parser', () => { const { deps } = parseGradle(input); expect(deps).toMatchObject([output].filter(is.truthy)); }); + + it('handles 3 independent dependencies mismatched as groupId, artifactId, version', () => { + const { deps } = parseGradle( + 'someConfig("foo:bar:1.2.3", "foo:baz:4.5.6", "foo:qux:7.8.9")', + ); + expect(deps).toMatchObject([ + { depName: 'foo:bar', currentValue: '1.2.3' }, + { depName: 'foo:baz', currentValue: '4.5.6' }, + { depName: 'foo:qux', currentValue: '7.8.9' }, + ]); + }); }); describe('calculations', () => { @@ -792,7 +811,106 @@ describe('modules/manager/gradle/parser', () => { content.slice(managerData!.fileReplacePosition).indexOf(currentValue!), ); expect(replacementIndices.every((idx) => idx === 0)).toBeTrue(); - expect(deps).toMatchSnapshot(); + expect(deps).toMatchObject([ + { + currentValue: '1.5.2.RELEASE', + depName: 'org.springframework.boot:spring-boot-gradle-plugin', + sharedVariableName: 'springBootVersion', + managerData: { + fileReplacePosition: 53, + packageFile: 'build.gradle', + }, + }, + { + currentValue: '1.2.3', + depName: 'com.github.jengelman.gradle.plugins:shadow', + managerData: { + fileReplacePosition: 417, + packageFile: 'build.gradle', + }, + }, + { + currentValue: '0.1', + depName: 'com.fkorotkov:gradle-libraries-plugin', + managerData: { + fileReplacePosition: 481, + packageFile: 'build.gradle', + }, + }, + { + currentValue: '0.2.3', + depName: + 'gradle.plugin.se.patrikerdes:gradle-use-latest-versions-plugin', + managerData: { + fileReplacePosition: 568, + packageFile: 'build.gradle', + }, + }, + { + currentValue: '3.1.1', + depName: 'org.apache.openjpa:openjpa', + managerData: { + fileReplacePosition: 621, + packageFile: 'build.gradle', + }, + }, + { + currentValue: '0.13.0', + depName: 'com.gradle.publish:plugin-publish-plugin', + managerData: { + fileReplacePosition: 688, + packageFile: 'build.gradle', + }, + }, + { + currentValue: '6.0.9.RELEASE', + depName: 'org.grails:gorm-hibernate5-spring-boot', + managerData: { + fileReplacePosition: 1882, + packageFile: 'build.gradle', + }, + }, + { + currentValue: '6.0.5', + depName: 'mysql:mysql-connector-java', + managerData: { + fileReplacePosition: 1938, + packageFile: 'build.gradle', + }, + }, + { + currentValue: '1.0-groovy-2.4', + depName: 'org.spockframework:spock-spring', + managerData: { + fileReplacePosition: 1996, + packageFile: 'build.gradle', + }, + }, + { + currentValue: '1.3', + depName: 'org.hamcrest:hamcrest-core', + managerData: { + fileReplacePosition: 2101, + packageFile: 'build.gradle', + }, + }, + { + currentValue: '3.1', + depName: 'cglib:cglib-nodep', + managerData: { + fileReplacePosition: 2189, + packageFile: 'build.gradle', + }, + }, + { + currentValue: '3.1.1', + depName: 'org.apache.openjpa:openjpa', + managerData: { + fileReplacePosition: 2295, + packageFile: 'build.gradle', + }, + }, + ]); }); }); @@ -929,7 +1047,7 @@ describe('modules/manager/gradle/parser', () => { ${''} | ${'detekt { toolVersion = "1.2.3" }'} | ${{ depName: 'detekt', packageName: GRADLE_PLUGINS['detekt'][1], currentValue: '1.2.3' }} ${''} | ${'findbugs { toolVersion = "1.2.3" }'} | ${{ depName: 'findbugs', packageName: GRADLE_PLUGINS['findbugs'][1], currentValue: '1.2.3' }} ${''} | ${'googleJavaFormat { toolVersion = "1.2.3" }'} | ${{ depName: 'googleJavaFormat', packageName: GRADLE_PLUGINS['googleJavaFormat'][1], currentValue: '1.2.3' }} - ${'baz = "1.2.3"'} | ${'jacoco { toolVersion = baz }'} | ${{ depName: 'jacoco', packageName: GRADLE_PLUGINS['jacoco'][1], currentValue: '1.2.3', groupName: 'baz' }} + ${'baz = "1.2.3"'} | ${'jacoco { toolVersion = baz }'} | ${{ depName: 'jacoco', packageName: GRADLE_PLUGINS['jacoco'][1], currentValue: '1.2.3', sharedVariableName: 'baz' }} ${'baz = "1.2.3"'} | ${'jacoco { toolVersion = property("baz") }'} | ${{ depName: 'jacoco', packageName: GRADLE_PLUGINS['jacoco'][1], currentValue: '1.2.3' }} ${''} | ${'lombok { version = "1.2.3" }'} | ${{ depName: 'lombok', packageName: GRADLE_PLUGINS['lombok'][1], currentValue: '1.2.3' }} ${''} | ${'lombok { version.set("1.2.3") }'} | ${{ depName: 'lombok', packageName: GRADLE_PLUGINS['lombok'][1], currentValue: '1.2.3' }} @@ -954,6 +1072,21 @@ describe('modules/manager/gradle/parser', () => { }); }); + describe('implicit gradle test suite dependencies', () => { + it.each` + def | input | output + ${''} | ${'testing.suites.test { useJunit("1.2.3") } } }'} | ${{ depName: 'useJunit', packageName: GRADLE_TEST_SUITES['useJunit'], currentValue: '1.2.3' }} + ${'baz = "1.2.3"'} | ${'testing.suites.test { useJUnitJupiter(baz) } } }'} | ${{ depName: 'useJUnitJupiter', packageName: GRADLE_TEST_SUITES['useJUnitJupiter'], currentValue: '1.2.3' }} + ${''} | ${'testing.suites.test { useKotlinTest("1.2.3") } } }'} | ${{ depName: 'useKotlinTest', packageName: GRADLE_TEST_SUITES['useKotlinTest'], currentValue: '1.2.3' }} + ${'baz = "1.2.3"'} | ${'testing { suites { test { useSpock(baz) } } }'} | ${{ depName: 'useSpock', packageName: GRADLE_TEST_SUITES['useSpock'], currentValue: '1.2.3' }} + ${''} | ${'testing.suites.test { useSpock("1.2.3") } } }'} | ${{ depName: 'useSpock', packageName: GRADLE_TEST_SUITES['useSpock'], currentValue: '1.2.3' }} + ${''} | ${'testing.suites.test { useTestNG("1.2.3") } } }'} | ${{ depName: 'useTestNG', packageName: GRADLE_TEST_SUITES['useTestNG'], currentValue: '1.2.3' }} + `('$def | $input', ({ def, input, output }) => { + const { deps } = parseGradle([def, input].join('\n')); + expect(deps).toMatchObject([output]); + }); + }); + describe('Kotlin object notation', () => { it('simple objects', () => { const input = codeBlock` @@ -983,7 +1116,7 @@ describe('modules/manager/gradle/parser', () => { deps: [ { depName: 'org.slf4j:slf4j-api', - groupName: 'Versions.baz', + sharedVariableName: 'Versions.baz', currentValue: '1.2.3', }, { @@ -992,17 +1125,17 @@ describe('modules/manager/gradle/parser', () => { }, { depName: 'androidx.core:core-ktx', - groupName: 'Versions.baz', + sharedVariableName: 'Versions.baz', currentValue: '1.2.3', }, { depName: 'androidx.webkit:webkit', - groupName: 'Versions.baz', + sharedVariableName: 'Versions.baz', currentValue: '1.2.3', }, { depName: 'foo:bar', - groupName: 'Versions.baz', + sharedVariableName: 'Versions.baz', currentValue: '1.2.3', }, ], @@ -1056,22 +1189,22 @@ describe('modules/manager/gradle/parser', () => { { depName: 'org.jetbrains.kotlin:kotlin-stdlib-jdk7', currentValue: '1.5.31', - groupName: 'Deps.kotlinVersion', + sharedVariableName: 'Deps.kotlinVersion', }, { depName: 'androidx.test:core', currentValue: '1.3.0-rc01', - groupName: 'Deps.Test.version', + sharedVariableName: 'Deps.Test.version', }, { depName: 'androidx.test.espresso:espresso-core', currentValue: '3.3.0-rc01', - groupName: 'Deps.Test.Espresso.Release.version', + sharedVariableName: 'Deps.Test.Espresso.Release.version', }, { depName: 'androidx.test:core-ktx', currentValue: '1.3.0-rc01', - groupName: 'Deps.Test.version', + sharedVariableName: 'Deps.Test.version', }, ], }); @@ -1108,7 +1241,7 @@ describe('modules/manager/gradle/parser', () => { { depName: 'com.h2database:h2', currentValue: '2.0.206', - groupName: 'ModuleConfiguration.Build.Database.h2Version', + sharedVariableName: 'ModuleConfiguration.Build.Database.h2Version', }, ], }); diff --git a/lib/modules/manager/gradle/parser.ts b/lib/modules/manager/gradle/parser.ts index d9f0f4e6111792..b0459572b2d084 100644 --- a/lib/modules/manager/gradle/parser.ts +++ b/lib/modules/manager/gradle/parser.ts @@ -18,7 +18,7 @@ import type { PackageVariables, ParseGradleResult, } from './types'; -import { isDependencyString, parseDependencyString } from './utils'; +import { parseDependencyString } from './utils'; const groovy = lang.createLang('groovy'); const ctx: Ctx = { @@ -127,28 +127,27 @@ export function parseProps( ): { vars: PackageVariables; deps: PackageDependency[] } { let offset = 0; const vars: PackageVariables = {}; - const deps: PackageDependency[] = []; + const deps: PackageDependency[] = []; + for (const line of input.split(newlineRegex)) { const lineMatch = propRegex.exec(line); if (lineMatch?.groups) { const { key, value, leftPart } = lineMatch.groups; - if (isDependencyString(value)) { - const dep = parseDependencyString(value); - if (dep) { - deps.push({ - ...dep, - managerData: { - fileReplacePosition: - offset + leftPart.length + dep.depName!.length + 1, - packageFile, - }, - }); - } + const replacePosition = offset + leftPart.length; + const dep = parseDependencyString(value); + if (dep) { + deps.push({ + ...dep, + managerData: { + fileReplacePosition: replacePosition + dep.depName!.length + 1, + packageFile, + }, + }); } else { vars[key] = { key, value, - fileReplacePosition: offset + leftPart.length, + fileReplacePosition: replacePosition, packageFile, }; } diff --git a/lib/modules/manager/gradle/parser/common.ts b/lib/modules/manager/gradle/parser/common.ts index fffc610c0cac41..0a9c0c8082edf2 100644 --- a/lib/modules/manager/gradle/parser/common.ts +++ b/lib/modules/manager/gradle/parser/common.ts @@ -37,6 +37,14 @@ export const GRADLE_PLUGINS = { spotbugs: ['toolVersion', 'com.github.spotbugs:spotbugs'], }; +export const GRADLE_TEST_SUITES = { + useJunit: 'junit:junit', + useJUnitJupiter: 'org.junit.jupiter:junit-jupiter', + useKotlinTest: 'org.jetbrains.kotlin:kotlin-test-junit', + useSpock: 'org.spockframework:spock-core', + useTestNG: 'org.testng:testng', +}; + export function storeVarToken(ctx: Ctx, node: lexer.Token): Ctx { ctx.varTokens.push(node); return ctx; @@ -267,23 +275,13 @@ export const qTemplateString = q ctx.tmpTokenStore.templateTokens = []; return ctx; }, - search: q.alt( - qStringValue.handler((ctx) => { - ctx.tmpTokenStore.templateTokens?.push(...ctx.varTokens); - ctx.varTokens = []; - return ctx; - }), - qPropertyAccessIdentifier.handler((ctx) => { + search: q + .alt(qStringValue, qPropertyAccessIdentifier, qVariableAccessIdentifier) + .handler((ctx) => { ctx.tmpTokenStore.templateTokens?.push(...ctx.varTokens); ctx.varTokens = []; return ctx; }), - qVariableAccessIdentifier.handler((ctx) => { - ctx.tmpTokenStore.templateTokens?.push(...ctx.varTokens); - ctx.varTokens = []; - return ctx; - }), - ), }) .handler((ctx) => { ctx.varTokens = ctx.tmpTokenStore.templateTokens!; @@ -321,14 +319,24 @@ export const qDotOrBraceExpr = ( matcher: q.QueryBuilder, ): q.QueryBuilder => q.sym(symValue).alt( - q.alt( - q.op('.').join(matcher), - q.tree({ - type: 'wrapped-tree', - maxDepth: 1, - startsWith: '{', - endsWith: '}', - search: matcher, - }), - ), + q.op('.').join(matcher), + q.tree({ + type: 'wrapped-tree', + maxDepth: 1, + startsWith: '{', + endsWith: '}', + search: matcher, + }), ); + +export const qGroupId = qValueMatcher.handler((ctx) => + storeInTokenMap(ctx, 'groupId'), +); + +export const qArtifactId = qValueMatcher.handler((ctx) => + storeInTokenMap(ctx, 'artifactId'), +); + +export const qVersion = qValueMatcher.handler((ctx) => + storeInTokenMap(ctx, 'version'), +); diff --git a/lib/modules/manager/gradle/parser/dependencies.ts b/lib/modules/manager/gradle/parser/dependencies.ts index 93e029da4037bf..edf48a3fe323e0 100644 --- a/lib/modules/manager/gradle/parser/dependencies.ts +++ b/lib/modules/manager/gradle/parser/dependencies.ts @@ -3,32 +3,24 @@ import { regEx } from '../../../../util/regex'; import type { Ctx } from '../types'; import { GRADLE_PLUGINS, + GRADLE_TEST_SUITES, cleanupTempVars, + qArtifactId, qDotOrBraceExpr, + qGroupId, qTemplateString, qValueMatcher, + qVersion, storeInTokenMap, storeVarToken, } from './common'; import { handleDepString, - handleImplicitGradlePlugin, + handleImplicitDep, handleKotlinShortNotationDep, handleLongFormDep, } from './handlers'; -const qGroupId = qValueMatcher.handler((ctx) => - storeInTokenMap(ctx, 'groupId'), -); - -const qArtifactId = qValueMatcher.handler((ctx) => - storeInTokenMap(ctx, 'artifactId'), -); - -const qVersion = qValueMatcher.handler((ctx) => - storeInTokenMap(ctx, 'version'), -); - // "foo:bar:1.2.3" // "foo:bar:$baz" // "foo" + "${bar}" + baz @@ -164,10 +156,10 @@ export const qLongFormDep = q // pmd { toolVersion = "1.2.3" } const qImplicitGradlePlugin = q .alt( - ...Object.keys(GRADLE_PLUGINS).map((pluginName) => + ...Object.keys(GRADLE_PLUGINS).map((implicitDepName) => q - .sym(pluginName, storeVarToken) - .handler((ctx) => storeInTokenMap(ctx, 'pluginName')) + .sym(implicitDepName, storeVarToken) + .handler((ctx) => storeInTokenMap(ctx, 'implicitDepName')) .tree({ type: 'wrapped-tree', maxDepth: 1, @@ -176,7 +168,7 @@ const qImplicitGradlePlugin = q endsWith: '}', search: q .sym( - GRADLE_PLUGINS[pluginName as keyof typeof GRADLE_PLUGINS][0], + GRADLE_PLUGINS[implicitDepName as keyof typeof GRADLE_PLUGINS][0], ) .alt( // toolVersion = "1.2.3" @@ -195,7 +187,34 @@ const qImplicitGradlePlugin = q }), ), ) - .handler(handleImplicitGradlePlugin) + .handler(handleImplicitDep) + .handler(cleanupTempVars); + +// testing { suites { test { useSpock("1.2.3") } } } +const qImplicitTestSuites = qDotOrBraceExpr( + 'testing', + qDotOrBraceExpr( + 'suites', + qDotOrBraceExpr( + 'test', + q + .sym( + regEx(`^(?:${Object.keys(GRADLE_TEST_SUITES).join('|')})$`), + storeVarToken, + ) + .handler((ctx) => storeInTokenMap(ctx, 'implicitDepName')) + .tree({ + type: 'wrapped-tree', + maxDepth: 1, + maxMatches: 1, + startsWith: '(', + endsWith: ')', + search: q.begin().join(qVersion).end(), + }), + ), + ), +) + .handler(handleImplicitDep) .handler(cleanupTempVars); export const qDependencies = q.alt( @@ -205,6 +224,7 @@ export const qDependencies = q.alt( qKotlinShortNotationDependencies, qKotlinMapNotationDependencies, qImplicitGradlePlugin, + qImplicitTestSuites, // avoid heuristic matching of gradle feature variant capabilities qDotOrBraceExpr('java', q.sym('registerFeature').tree()), ); diff --git a/lib/modules/manager/gradle/parser/handlers.ts b/lib/modules/manager/gradle/parser/handlers.ts index 229671b249eaed..5d983abbd07f31 100644 --- a/lib/modules/manager/gradle/parser/handlers.ts +++ b/lib/modules/manager/gradle/parser/handlers.ts @@ -6,9 +6,10 @@ import { regEx } from '../../../../util/regex'; import type { PackageDependency } from '../../types'; import type { parseGradle as parseGradleCallback } from '../parser'; import type { Ctx, GradleManagerData } from '../types'; -import { parseDependencyString } from '../utils'; +import { isDependencyString, parseDependencyString } from '../utils'; import { GRADLE_PLUGINS, + GRADLE_TEST_SUITES, REGISTRY_URLS, findVariable, interpolateString, @@ -40,7 +41,7 @@ export function handleAssignment(ctx: Ctx): Ctx { // = string value const dep = parseDependencyString(valTokens[0].value); if (dep) { - dep.groupName = key; + dep.sharedVariableName = key; dep.managerData = { fileReplacePosition: valTokens[0].offset + dep.depName!.length + 1, packageFile: ctx.packageFile, @@ -82,7 +83,7 @@ export function handleDepString(ctx: Ctx): Ctx { fileReplacePosition = varData.fileReplacePosition; if (varData.value === dep.currentValue) { dep.managerData = { fileReplacePosition, packageFile }; - dep.groupName = varData.key; + dep.sharedVariableName = varData.key; } } } @@ -102,7 +103,7 @@ export function handleDepString(ctx: Ctx): Ctx { fileReplacePosition = lastToken.offset + lastToken.value.lastIndexOf(dep.currentValue); } - delete dep.groupName; + delete dep.sharedVariableName; } else { dep.skipReason = 'contains-variable'; } @@ -143,7 +144,7 @@ export function handleKotlinShortNotationDep(ctx: Ctx): Ctx { } else if (versionTokens[0].type === 'symbol') { const varData = findVariable(versionTokens[0].value, ctx); if (varData) { - dep.groupName = varData.key; + dep.sharedVariableName = varData.key; dep.currentValue = varData.value; dep.managerData = { fileReplacePosition: varData.fileReplacePosition, @@ -169,6 +170,22 @@ export function handleLongFormDep(ctx: Ctx): Ctx { return ctx; } + // Special handling: 3 independent dependencies mismatched as groupId, artifactId, version + if ( + isDependencyString(groupId) && + isDependencyString(artifactId) && + isDependencyString(version) + ) { + ctx.tokenMap.templateStringTokens = groupIdTokens; + handleDepString(ctx); + ctx.tokenMap.templateStringTokens = artifactIdTokens; + handleDepString(ctx); + ctx.tokenMap.templateStringTokens = versionTokens; + handleDepString(ctx); + + return ctx; + } + const dep = parseDependencyString([groupId, artifactId, version].join(':')); if (!dep) { return ctx; @@ -181,7 +198,7 @@ export function handleLongFormDep(ctx: Ctx): Ctx { } else if (versionTokens[0].type === 'symbol') { const varData = findVariable(versionTokens[0].value, ctx); if (varData) { - dep.groupName = varData.key; + dep.sharedVariableName = varData.key; dep.managerData = { fileReplacePosition: varData.fileReplacePosition, packageFile: varData.packageFile, @@ -190,7 +207,7 @@ export function handleLongFormDep(ctx: Ctx): Ctx { } else { // = string value if (methodName?.[0]?.value === 'dependencySet') { - dep.groupName = `${groupId}:${version}`; + dep.sharedVariableName = `${groupId}:${version}`; } dep.managerData = { fileReplacePosition: versionTokens[0].offset, @@ -231,7 +248,7 @@ export function handlePlugin(ctx: Ctx): Ctx { } else if (pluginVersion[0].type === 'symbol') { const varData = findVariable(pluginVersion[0].value, ctx); if (varData) { - dep.groupName = varData.key; + dep.sharedVariableName = varData.key; dep.currentValue = varData.value; dep.managerData = { fileReplacePosition: varData.fileReplacePosition, @@ -385,22 +402,25 @@ export function handleApplyFrom(ctx: Ctx): Ctx { return ctx; } -export function handleImplicitGradlePlugin(ctx: Ctx): Ctx { - const pluginName = loadFromTokenMap(ctx, 'pluginName')[0].value; +export function handleImplicitDep(ctx: Ctx): Ctx { + const implicitDepName = loadFromTokenMap(ctx, 'implicitDepName')[0].value; const versionTokens = loadFromTokenMap(ctx, 'version'); const versionValue = interpolateString(versionTokens, ctx); if (!versionValue) { return ctx; } - const groupIdArtifactId = - GRADLE_PLUGINS[pluginName as keyof typeof GRADLE_PLUGINS][1]; + const isImplicitGradlePlugin = implicitDepName in GRADLE_PLUGINS; + const groupIdArtifactId = isImplicitGradlePlugin + ? GRADLE_PLUGINS[implicitDepName as keyof typeof GRADLE_PLUGINS][1] + : GRADLE_TEST_SUITES[implicitDepName as keyof typeof GRADLE_TEST_SUITES]; + const dep = parseDependencyString(`${groupIdArtifactId}:${versionValue}`); if (!dep) { return ctx; } - dep.depName = pluginName; + dep.depName = implicitDepName; dep.packageName = groupIdArtifactId; dep.managerData = { fileReplacePosition: versionTokens[0].offset, @@ -413,7 +433,7 @@ export function handleImplicitGradlePlugin(ctx: Ctx): Ctx { } else if (versionTokens[0].type === 'symbol') { const varData = findVariable(versionTokens[0].value, ctx); if (varData) { - dep.groupName = varData.key; + dep.sharedVariableName = varData.key; dep.currentValue = varData.value; dep.managerData = { fileReplacePosition: varData.fileReplacePosition, diff --git a/lib/modules/manager/gradle/parser/plugins.ts b/lib/modules/manager/gradle/parser/plugins.ts index 5cf27b58c11891..aebb081067abe2 100644 --- a/lib/modules/manager/gradle/parser/plugins.ts +++ b/lib/modules/manager/gradle/parser/plugins.ts @@ -4,16 +4,12 @@ import type { Ctx } from '../types'; import { cleanupTempVars, qStringValue, - qValueMatcher, + qVersion, storeInTokenMap, storeVarToken, } from './common'; import { handlePlugin } from './handlers'; -const qVersion = qValueMatcher.handler((ctx) => - storeInTokenMap(ctx, 'version'), -); - export const qPlugins = q .sym(regEx(/^(?:id|kotlin)$/), storeVarToken) .handler((ctx) => storeInTokenMap(ctx, 'methodName')) diff --git a/lib/modules/manager/gradle/parser/registry-urls.ts b/lib/modules/manager/gradle/parser/registry-urls.ts index 341dc37d547ec2..e684135f808852 100644 --- a/lib/modules/manager/gradle/parser/registry-urls.ts +++ b/lib/modules/manager/gradle/parser/registry-urls.ts @@ -32,6 +32,7 @@ const qUri = q // mavenCentral { ... } const qPredefinedRegistries = q .sym(regEx(`^(?:${Object.keys(REGISTRY_URLS).join('|')})$`), storeVarToken) + .handler((ctx) => storeInTokenMap(ctx, 'registryUrl')) .alt( q.tree({ type: 'wrapped-tree', @@ -45,10 +46,31 @@ const qPredefinedRegistries = q endsWith: '}', }), ) - .handler((ctx) => storeInTokenMap(ctx, 'registryUrl')) .handler(handlePredefinedRegistryUrl) .handler(cleanupTempVars); +// { url = "https://some.repo" } +const qMavenArtifactRegistry = q.tree({ + type: 'wrapped-tree', + maxDepth: 1, + startsWith: '{', + endsWith: '}', + search: q.alt( + q + .sym('name') + .opt(q.op('=')) + .join(qValueMatcher) + .handler((ctx) => storeInTokenMap(ctx, 'name')), + q.sym('url').opt(q.op('=')).join(qUri), + q.sym('setUrl').tree({ + maxDepth: 1, + startsWith: '(', + endsWith: ')', + search: q.begin().join(qUri).end(), + }), + ), +}); + // maven(url = uri("https://foo.bar/baz")) // maven { name = some; url = "https://foo.bar/${name}" } const qCustomRegistryUrl = q @@ -61,26 +83,7 @@ const qCustomRegistryUrl = q endsWith: ')', search: q.begin().opt(q.sym('url').op('=')).join(qUri).end(), }), - q.tree({ - type: 'wrapped-tree', - maxDepth: 1, - startsWith: '{', - endsWith: '}', - search: q.alt( - q - .sym('name') - .opt(q.op('=')) - .join(qValueMatcher) - .handler((ctx) => storeInTokenMap(ctx, 'name')), - q.sym('url').opt(q.op('=')).join(qUri), - q.sym('setUrl').tree({ - maxDepth: 1, - startsWith: '(', - endsWith: ')', - search: q.begin().join(qUri).end(), - }), - ), - }), + qMavenArtifactRegistry, ) .handler(handleCustomRegistryUrl) .handler(cleanupTempVars); diff --git a/lib/modules/manager/gradle/parser/version-catalogs.ts b/lib/modules/manager/gradle/parser/version-catalogs.ts index 272688ee6084b1..c4c1259ccf424d 100644 --- a/lib/modules/manager/gradle/parser/version-catalogs.ts +++ b/lib/modules/manager/gradle/parser/version-catalogs.ts @@ -2,6 +2,8 @@ import { query as q } from 'good-enough-parser'; import type { Ctx } from '../types'; import { cleanupTempVars, + qArtifactId, + qGroupId, qStringValue, qStringValueAsSymbol, qValueMatcher, @@ -10,14 +12,6 @@ import { } from './common'; import { handleLibraryDep, handlePlugin } from './handlers'; -const qGroupId = qValueMatcher.handler((ctx) => - storeInTokenMap(ctx, 'groupId'), -); - -const qArtifactId = qValueMatcher.handler((ctx) => - storeInTokenMap(ctx, 'artifactId'), -); - const qVersionCatalogVersion = q .op('.') .alt( diff --git a/lib/modules/manager/gradle/update.spec.ts b/lib/modules/manager/gradle/update.spec.ts index 4d0a77623d5810..cd46e12ca1a167 100644 --- a/lib/modules/manager/gradle/update.spec.ts +++ b/lib/modules/manager/gradle/update.spec.ts @@ -23,7 +23,7 @@ describe('modules/manager/gradle/update', () => { upgrade: { currentValue: '1.2.3', newValue: '1.2.5', - groupName: 'group', + sharedVariableName: 'group', managerData: { fileReplacePosition: 3, }, diff --git a/lib/modules/manager/gradle/update.ts b/lib/modules/manager/gradle/update.ts index 0839c2c7e20bb3..4286535ece0b47 100644 --- a/lib/modules/manager/gradle/update.ts +++ b/lib/modules/manager/gradle/update.ts @@ -23,7 +23,7 @@ export function updateDependency({ if (version === newValue) { return fileContent; } - if (version === currentValue || upgrade.groupName) { + if (version === currentValue || upgrade.sharedVariableName) { // TODO: types (#22198) return `${leftPart}${newValue}${restPart}`; } diff --git a/lib/modules/manager/gradle/utils.spec.ts b/lib/modules/manager/gradle/utils.spec.ts index 1de059b86c62b9..d8879d0ebf2bd3 100644 --- a/lib/modules/manager/gradle/utils.spec.ts +++ b/lib/modules/manager/gradle/utils.spec.ts @@ -1,7 +1,13 @@ -import type { VariableRegistry } from './types'; +import type { PackageVariables, VariableRegistry } from './types'; import { getVars, isDependencyString, + isGradleBuildFile, + isGradleScriptFile, + isGradleVersionsFile, + isKotlinSourceFile, + isPropsFile, + isTOMLFile, parseDependencyString, reorderFiles, toAbsolutePath, @@ -10,66 +16,83 @@ import { } from './utils'; describe('modules/manager/gradle/utils', () => { - it('versionLikeSubstring', () => { - [ - '1.2.3', - '[1.0,2.0]', - '(,2.0[', - '2.1.1.RELEASE', - '1.0.+', - '2022-05-10_55', - ].forEach((input) => { - expect(versionLikeSubstring(input)).toEqual(input); - expect(versionLikeSubstring(`${input}'`)).toEqual(input); - expect(versionLikeSubstring(`${input}"`)).toEqual(input); - expect(versionLikeSubstring(`${input}\n`)).toEqual(input); - expect(versionLikeSubstring(`${input} `)).toEqual(input); - expect(versionLikeSubstring(`${input}$`)).toEqual(input); + describe('versionLikeSubstring', () => { + it('extracts the actual version', () => { + const inputs = [ + '1.2.3', + '[1.0,2.0]', + '(,2.0[', + '2.1.1.RELEASE', + '1.0.+', + '2022-05-10_55', + ]; + const suffixes = ['', "'", '"', '\n', ' ', '$']; + + for (const input of inputs) { + for (const suffix of suffixes) { + expect(versionLikeSubstring(`${input}${suffix}`)).toEqual(input); + } + } }); - expect(versionLikeSubstring('')).toBeNull(); - expect(versionLikeSubstring(undefined)).toBeNull(); - expect(versionLikeSubstring(null)).toBeNull(); - expect(versionLikeSubstring('foobar')).toBeNull(); - expect(versionLikeSubstring('latest')).toBeNull(); - }); - it('isDependencyString', () => { - expect(isDependencyString('foo:bar:1.2.3')).toBeTrue(); - expect(isDependencyString('foo.foo:bar.bar:1.2.3')).toBeTrue(); - expect(isDependencyString('foo:bar:baz:qux')).toBeFalse(); - expect(isDependencyString('foo.bar:baz:1.2.3')).toBeTrue(); - expect(isDependencyString('foo.bar:baz:1.2.3:linux-cpu-x86_64')).toBeTrue(); - expect(isDependencyString('foo.bar:baz:1.2.+')).toBeTrue(); - expect(isDependencyString('foo:bar:baz:qux:quux')).toBeFalse(); - expect(isDependencyString("foo:bar:1.2.3'")).toBeFalse(); - expect(isDependencyString('foo:bar:1.2.3"')).toBeFalse(); - expect(isDependencyString('-Xep:ParameterName:OFF')).toBeFalse(); - expect(isDependencyString('foo$bar:baz:1.2.+')).toBeFalse(); - expect(isDependencyString('scm:git:https://some.git')).toBeFalse(); + it('returns null for invalid inputs', () => { + const inputs = ['', undefined, null, 'foobar', 'latest']; + for (const input of inputs) { + expect(versionLikeSubstring(input)).toBeNull(); + } + }); }); - it('parseDependencyString', () => { - expect(parseDependencyString('foo:bar:1.2.3')).toMatchObject({ - depName: 'foo:bar', - currentValue: '1.2.3', + describe('isDependencyString', () => { + it.each` + input | output + ${'foo:bar:1.2.3'} | ${true} + ${'foo.foo:bar.bar:1.2.3'} | ${true} + ${'foo.bar:baz:1.2.3'} | ${true} + ${'foo.bar:baz:1.2.3:linux-cpu-x86_64'} | ${true} + ${'foo:bar:1.2.3@zip'} | ${true} + ${'foo:bar:x86@x86'} | ${true} + ${'foo.bar:baz:1.2.+'} | ${true} + ${'foo:bar:baz:qux'} | ${false} + ${'foo:bar:baz:qux:quux'} | ${false} + ${"foo:bar:1.2.3'"} | ${false} + ${'foo:bar:1.2.3"'} | ${false} + ${'-Xep:ParameterName:OFF'} | ${false} + ${'foo$bar:baz:1.2.+'} | ${false} + ${'scm:git:https://some.git'} | ${false} + ${'foo.bar:baz:1.2.3:linux-cpu$-x86_64'} | ${false} + ${'foo:bar:1.2.3@zip@foo'} | ${false} + `('$input', ({ input, output }) => { + expect(isDependencyString(input)).toBe(output); }); - expect(parseDependencyString('foo.foo:bar.bar:1.2.3')).toMatchObject({ - depName: 'foo.foo:bar.bar', - currentValue: '1.2.3', - }); - expect(parseDependencyString('foo.bar:baz:1.2.3')).toMatchObject({ - depName: 'foo.bar:baz', - currentValue: '1.2.3', - }); - expect(parseDependencyString('foo:bar:1.2.+')).toMatchObject({ - depName: 'foo:bar', - currentValue: '1.2.+', + }); + + describe('parseDependencyString', () => { + it.each` + input | output + ${'foo:bar:1.2.3'} | ${{ depName: 'foo:bar', currentValue: '1.2.3' }} + ${'foo.foo:bar.bar:1.2.3'} | ${{ depName: 'foo.foo:bar.bar', currentValue: '1.2.3' }} + ${'foo.bar:baz:1.2.3'} | ${{ depName: 'foo.bar:baz', currentValue: '1.2.3' }} + ${'foo:bar:1.2.+'} | ${{ depName: 'foo:bar', currentValue: '1.2.+' }} + ${'foo:bar:1.2.3@zip'} | ${{ depName: 'foo:bar', currentValue: '1.2.3', dataType: 'zip' }} + ${'foo:bar:baz:qux'} | ${null} + ${'foo:bar:baz:qux:quux'} | ${null} + ${"foo:bar:1.2.3'"} | ${null} + ${'foo:bar:1.2.3"'} | ${null} + ${'-Xep:ParameterName:OFF'} | ${null} + `('$input', ({ input, output }) => { + expect(parseDependencyString(input)).toEqual(output); }); - expect(parseDependencyString('foo:bar:baz:qux')).toBeNull(); - expect(parseDependencyString('foo:bar:baz:qux:quux')).toBeNull(); - expect(parseDependencyString("foo:bar:1.2.3'")).toBeNull(); - expect(parseDependencyString('foo:bar:1.2.3"')).toBeNull(); - expect(parseDependencyString('-Xep:ParameterName:OFF')).toBeNull(); + }); + + it('filetype checks', () => { + expect(isGradleScriptFile('/a/Somefile.gradle.kts')).toBeTrue(); + expect(isGradleScriptFile('/a/Somefile.gradle')).toBeTrue(); + expect(isGradleVersionsFile('/a/versions.gradle.kts')).toBeTrue(); + expect(isGradleBuildFile('/a/build.gradle')).toBeTrue(); + expect(isPropsFile('/a/gradle.properties')).toBeTrue(); + expect(isKotlinSourceFile('/a/Somefile.kt')).toBeTrue(); + expect(isTOMLFile('/a/Somefile.toml')).toBeTrue(); }); it('reorderFiles', () => { @@ -175,20 +198,33 @@ describe('modules/manager/gradle/utils', () => { }); }); - it('updateVars', () => { - const registry: VariableRegistry = { - [toAbsolutePath('/foo/bar/baz')]: { + describe('updateVars', () => { + it('empty registry', () => { + const registry: VariableRegistry = {}; + const newVars: PackageVariables = { + qux: { key: 'qux', value: 'qux' }, + }; + updateVars(registry, '/foo/bar/baz', newVars); + expect(registry).toStrictEqual({ '/foo/bar/baz': newVars }); + }); + + it('updates the registry', () => { + const registry: VariableRegistry = { + [toAbsolutePath('/foo/bar/baz')]: { + bar: { key: 'bar', value: 'bar' }, + baz: { key: 'baz', value: 'baz' }, + }, + }; + + updateVars(registry, '/foo/bar/baz', { + qux: { key: 'qux', value: 'qux' }, + }); + const res = getVars(registry, '/foo/bar/baz/build.gradle'); + expect(res).toStrictEqual({ bar: { key: 'bar', value: 'bar' }, baz: { key: 'baz', value: 'baz' }, - }, - }; - - updateVars(registry, '/foo/bar/baz', { qux: { key: 'qux', value: 'qux' } }); - const res = getVars(registry, '/foo/bar/baz/build.gradle'); - expect(res).toStrictEqual({ - bar: { key: 'bar', value: 'bar' }, - baz: { key: 'baz', value: 'baz' }, - qux: { key: 'qux', value: 'qux' }, + qux: { key: 'qux', value: 'qux' }, + }); }); }); }); diff --git a/lib/modules/manager/gradle/utils.ts b/lib/modules/manager/gradle/utils.ts index 84de600b478a0f..8b238247ec5300 100644 --- a/lib/modules/manager/gradle/utils.ts +++ b/lib/modules/manager/gradle/utils.ts @@ -13,8 +13,7 @@ const artifactRegex = regEx( const versionLikeRegex = regEx('^(?[-_.\\[\\](),a-zA-Z0-9+]+)'); -// Extracts version-like and range-like strings -// from the beginning of input +// Extracts version-like and range-like strings from the beginning of input export function versionLikeSubstring( input: string | null | undefined, ): string | null { @@ -32,40 +31,32 @@ export function versionLikeSubstring( } export function isDependencyString(input: string): boolean { - const split = input?.split(':'); - if (split?.length !== 3 && split?.length !== 4) { + const parts = input.split(':'); + if (parts.length !== 3 && parts.length !== 4) { return false; } - // eslint-disable-next-line prefer-const - let [tempGroupId, tempArtifactId, tempVersionPart, optionalClassifier] = - split; + const [groupId, artifactId, versionPart, optionalClassifier] = parts; if (optionalClassifier && !artifactRegex.test(optionalClassifier)) { return false; } - if ( - tempVersionPart !== versionLikeSubstring(tempVersionPart) && - tempVersionPart.includes('@') - ) { - const versionSplit = tempVersionPart?.split('@'); - if (versionSplit?.length !== 2) { + let version = versionPart; + if (versionPart.includes('@')) { + const [actualVersion, ...rest] = versionPart.split('@'); + if (rest.length !== 1) { return false; } - [tempVersionPart] = versionSplit; + version = actualVersion; } - const [groupId, artifactId, versionPart] = [ - tempGroupId, - tempArtifactId, - tempVersionPart, - ]; + return !!( groupId && artifactId && - versionPart && + version && artifactRegex.test(groupId) && artifactRegex.test(artifactId) && - versionPart === versionLikeSubstring(versionPart) + version === versionLikeSubstring(version) ); } @@ -75,18 +66,14 @@ export function parseDependencyString( if (!isDependencyString(input)) { return null; } - const [groupId, artifactId, FullValue] = input.split(':'); - if (FullValue === versionLikeSubstring(FullValue)) { - return { - depName: `${groupId}:${artifactId}`, - currentValue: FullValue, - }; - } - const [currentValue, dataType] = FullValue.split('@'); + + const [groupId, artifactId, fullValue] = input.split(':'); + const [currentValue, dataType] = fullValue.split('@'); + return { depName: `${groupId}:${artifactId}`, currentValue, - dataType, + ...(dataType && { dataType }), }; } diff --git a/lib/modules/manager/haskell-cabal/extract.spec.ts b/lib/modules/manager/haskell-cabal/extract.spec.ts new file mode 100644 index 00000000000000..d4f680e75f74c0 --- /dev/null +++ b/lib/modules/manager/haskell-cabal/extract.spec.ts @@ -0,0 +1,113 @@ +import { + countPackageNameLength, + countPrecedingIndentation, + extractNamesAndRanges, + findDepends, + findExtents, + splitSingleDependency, +} from './extract'; + +const commentCabalFile = `build-depends: + -- leading + base, +-- middle + other, + -- trailing + other2`; + +describe('modules/manager/haskell-cabal/extract', () => { + describe('countPackageNameLength', () => { + it.each` + input | expected + ${'-'} | ${null} + ${'-j'} | ${null} + ${'-H'} | ${null} + ${'j-'} | ${null} + ${'3-'} | ${null} + ${'-3'} | ${null} + ${'3'} | ${null} + ${'ÃĻ'} | ${null} + ${'ÃĻe'} | ${null} + ${'j'} | ${1} + ${'H'} | ${1} + ${'0ad'} | ${3} + ${'3d'} | ${2} + ${'aeson'} | ${5} + ${'lens'} | ${4} + ${'parsec'} | ${6} + `('matches $input', ({ input, expected }) => { + const maybeIndex = countPackageNameLength(input); + expect(maybeIndex).toStrictEqual(expected); + }); + }); + + describe('countPrecedingIndentation()', () => { + it.each` + content | index | expected + ${'\tbuild-depends: base\n\tother-field: hi'} | ${1} | ${1} + ${' build-depends: base'} | ${1} | ${1} + ${'a\tb'} | ${0} | ${0} + ${'a\tb'} | ${2} | ${1} + ${'a b'} | ${2} | ${1} + ${' b'} | ${2} | ${2} + `( + 'countPrecedingIndentation($content, $index)', + ({ content, index, expected }) => { + expect(countPrecedingIndentation(content, index)).toBe(expected); + }, + ); + }); + + describe('findExtents()', () => { + it.each` + content | indent | expected + ${'a: b\n\tc: d'} | ${1} | ${10} + ${'a: b'} | ${2} | ${4} + ${'a: b\n\tc: d'} | ${2} | ${4} + ${'a: b\n '} | ${2} | ${6} + ${'a: b\n c: d\ne: f'} | ${1} | ${10} + `('findExtents($indent, $content)', ({ indent, content, expected }) => { + expect(findExtents(indent, content)).toBe(expected); + }); + }); + + describe('splitSingleDependency()', () => { + it.each` + depLine | expectedName | expectedRange + ${'base >=2 && <3'} | ${'base'} | ${'>=2 && <3'} + ${'base >=2 && <3 '} | ${'base'} | ${'>=2 && <3'} + ${'base>=2&&<3'} | ${'base'} | ${'>=2&&<3'} + ${'base'} | ${'base'} | ${''} + `( + 'splitSingleDependency($depLine)', + ({ depLine, expectedName, expectedRange }) => { + const res = splitSingleDependency(depLine); + expect(res?.name).toEqual(expectedName); + expect(res?.range).toEqual(expectedRange); + }, + ); + + // The first hyphen makes the package name invalid + expect(splitSingleDependency('-invalid-package-name')).toBeNull(); + }); + + describe('extractNamesAndRanges()', () => { + it('trims replaceString', () => { + const res = extractNamesAndRanges(' a , b '); + expect(res).toEqual([ + { currentValue: '', packageName: 'a', replaceString: 'a' }, + { currentValue: '', packageName: 'b', replaceString: 'b' }, + ]); + }); + }); + + describe('findDepends()', () => { + it('strips comments', () => { + const res = findDepends(commentCabalFile + '\na: b'); + expect(res).toEqual({ + buildDependsContent: '\n base,\n other,\n other2', + lengthProcessed: commentCabalFile.length, + }); + }); + }); +}); diff --git a/lib/modules/manager/haskell-cabal/extract.ts b/lib/modules/manager/haskell-cabal/extract.ts new file mode 100644 index 00000000000000..7abeee0c78e6db --- /dev/null +++ b/lib/modules/manager/haskell-cabal/extract.ts @@ -0,0 +1,206 @@ +import { regEx } from '../../../util/regex'; + +const buildDependsRegex = regEx( + /(?build-depends[ \t]*:)/i, +); +const commentRegex = regEx(/^[ \t]*--/); +function isNonASCII(str: string): boolean { + for (let i = 0; i < str.length; i++) { + if (str.charCodeAt(i) > 127) { + return true; + } + } + return false; +} + +export function countPackageNameLength(input: string): number | null { + if (input.length < 1 || isNonASCII(input)) { + return null; + } + if (!regEx(/^[A-Za-z0-9]/).test(input[0])) { + // Must start with letter or number + return null; + } + let idx = 1; + while (idx < input.length) { + if (regEx(/[A-Za-z0-9-]/).test(input[idx])) { + idx++; + } else { + break; + } + } + if (!regEx(/[A-Za-z]/).test(input.slice(0, idx))) { + // Must contain a letter + return null; + } + if (idx - 1 < input.length && input[idx - 1] === '-') { + // Can't end in a hyphen + return null; + } + return idx; +} + +export interface CabalDependency { + packageName: string; + currentValue: string; + replaceString: string; +} + +/** + * Find extents of field contents + * + * @param {number} indent - + * Indention level maintained within the block. + * Any indention lower than this means it's outside the field. + * Lines with this level or more are included in the field. + * @returns {number} + * Index just after the end of the block. + * Note that it may be after the end of the string. + */ +export function findExtents(indent: number, content: string): number { + let blockIdx: number = 0; + let mode: 'finding-newline' | 'finding-indention' = 'finding-newline'; + for (;;) { + if (mode === 'finding-newline') { + while (content[blockIdx++] !== '\n') { + if (blockIdx >= content.length) { + break; + } + } + if (blockIdx >= content.length) { + return content.length; + } + mode = 'finding-indention'; + } else { + let thisIndent = 0; + for (;;) { + if ([' ', '\t'].includes(content[blockIdx])) { + thisIndent += 1; + blockIdx++; + if (blockIdx >= content.length) { + return content.length; + } + continue; + } + mode = 'finding-newline'; + blockIdx++; + break; + } + if (thisIndent < indent) { + if (content.slice(blockIdx - 1, blockIdx + 1) === '--') { + // not enough indention, but the line is a comment, so include it + mode = 'finding-newline'; + continue; + } + // go back to before the newline + for (;;) { + if (content[blockIdx--] === '\n') { + break; + } + } + return blockIdx + 1; + } + mode = 'finding-newline'; + } + } +} + +/** + * Find indention level of build-depends + * + * @param {number} match - + * Search starts at this index, and proceeds backwards. + * @returns {number} + * Number of indention levels found before 'match'. + */ +export function countPrecedingIndentation( + content: string, + match: number, +): number { + let whitespaceIdx = match - 1; + let indent = 0; + while (whitespaceIdx >= 0 && [' ', '\t'].includes(content[whitespaceIdx])) { + indent += 1; + whitespaceIdx--; + } + return indent; +} + +/** + * Find one 'build-depends' field name usage and its field value + * + * @returns {{buildDependsContent: string, lengthProcessed: number}} + * buildDependsContent: + * the contents of the field, excluding the field name and the colon, + * and any comments within + * + * lengthProcessed: + * points to after the end of the field. Note that the field does _not_ + * necessarily start at `content.length - lengthProcessed`. + * + * Returns null if no 'build-depends' field is found. + */ +export function findDepends( + content: string, +): { buildDependsContent: string; lengthProcessed: number } | null { + const matchObj = buildDependsRegex.exec(content); + if (!matchObj?.groups) { + return null; + } + const indent = countPrecedingIndentation(content, matchObj.index); + const ourIdx: number = + matchObj.index + matchObj.groups['buildDependsFieldName'].length; + const extentLength: number = findExtents(indent + 1, content.slice(ourIdx)); + const extent = content.slice(ourIdx, ourIdx + extentLength); + const lines = []; + // Windows-style line breaks are fine because + // carriage returns are before the line feed. + for (const maybeCommentLine of extent.split('\n')) { + if (!commentRegex.test(maybeCommentLine)) { + lines.push(maybeCommentLine); + } + } + return { + buildDependsContent: lines.join('\n'), + lengthProcessed: ourIdx + extentLength, + }; +} + +/** + * Split a cabal single dependency into its constituent parts. + * The first part is the package name, an optional second part contains + * the version constraint. + * + * For example 'base == 3.2' would be split into 'base' and ' == 3.2'. + * + * @returns {{name: string, range: string}} + * Null if the trimmed string doesn't begin with a package name. + */ +export function splitSingleDependency( + input: string, +): { name: string; range: string } | null { + const match = countPackageNameLength(input); + if (match === null) { + return null; + } + const name: string = input.slice(0, match); + const range = input.slice(match).trim(); + return { name, range }; +} + +export function extractNamesAndRanges(content: string): CabalDependency[] { + const list = content.split(','); + const deps = []; + for (const untrimmedReplaceString of list) { + const replaceString = untrimmedReplaceString.trim(); + const maybeNameRange = splitSingleDependency(replaceString); + if (maybeNameRange !== null) { + deps.push({ + currentValue: maybeNameRange.range, + packageName: maybeNameRange.name, + replaceString, + }); + } + } + return deps; +} diff --git a/lib/modules/manager/haskell-cabal/index.spec.ts b/lib/modules/manager/haskell-cabal/index.spec.ts new file mode 100644 index 00000000000000..7409938d36745b --- /dev/null +++ b/lib/modules/manager/haskell-cabal/index.spec.ts @@ -0,0 +1,55 @@ +import { codeBlock } from 'common-tags'; +import { extractPackageFile, getRangeStrategy } from '.'; + +const minimalCabalFile = codeBlock` +cabal-version: 3.4 +name: minimal +version: 0.1.0.0 + +executable my-cli-entry-point + main-is: Main.hs + build-depends: base>=4.20`; + +describe('modules/manager/haskell-cabal/index', () => { + describe('extractPackageFile()', () => { + it.each` + content | expected + ${'build-depends: base,'} | ${['base']} + ${'build-depends:,other,other2'} | ${['other', 'other2']} + ${'build-depends : base'} | ${['base']} + ${'Build-Depends: base'} | ${['base']} + ${'build-depends: a\nbuild-depends: b'} | ${['a', 'b']} + ${'dependencies: base'} | ${[]} + `( + 'extractPackageFile($content).deps.map(x => x.packageName)', + ({ content, expected }) => { + expect( + extractPackageFile(content).deps.map((x) => x.packageName), + ).toStrictEqual(expected); + }, + ); + + expect(extractPackageFile(minimalCabalFile).deps).toStrictEqual([ + { + autoReplaceStringTemplate: '{{{depName}}} {{{newValue}}}', + currentValue: '>=4.20', + datasource: 'hackage', + depName: 'base', + packageName: 'base', + replaceString: 'base>=4.20', + versioning: 'pvp', + }, + ]); + }); + + describe('getRangeStrategy()', () => { + it.each` + input | expected + ${'auto'} | ${'widen'} + ${'widen'} | ${'widen'} + ${'replace'} | ${'replace'} + `('getRangeStrategy({ rangeStrategy: $input })', ({ input, expected }) => { + expect(getRangeStrategy({ rangeStrategy: input })).toBe(expected); + }); + }); +}); diff --git a/lib/modules/manager/haskell-cabal/index.ts b/lib/modules/manager/haskell-cabal/index.ts new file mode 100644 index 00000000000000..2616dd88f17d26 --- /dev/null +++ b/lib/modules/manager/haskell-cabal/index.ts @@ -0,0 +1,57 @@ +import type { Category } from '../../../constants'; +import type { RangeStrategy } from '../../../types'; +import { HackageDatasource } from '../../datasource/hackage'; +import * as pvpVersioning from '../../versioning/pvp'; +import type { + PackageDependency, + PackageFileContent, + RangeConfig, +} from '../types'; +import type { CabalDependency } from './extract'; +import { extractNamesAndRanges, findDepends } from './extract'; + +export const defaultConfig = { + fileMatch: ['\\.cabal$'], + pinDigests: false, +}; + +export const categories: Category[] = ['haskell']; + +export const supportedDatasources = [HackageDatasource.id]; + +export function extractPackageFile(content: string): PackageFileContent { + const deps = []; + let current = content; + for (;;) { + const maybeContent = findDepends(current); + if (maybeContent === null) { + break; + } + const cabalDeps: CabalDependency[] = extractNamesAndRanges( + maybeContent.buildDependsContent, + ); + for (const cabalDep of cabalDeps) { + const dep: PackageDependency = { + depName: cabalDep.packageName, + currentValue: cabalDep.currentValue, + datasource: HackageDatasource.id, + packageName: cabalDep.packageName, + versioning: pvpVersioning.id, + replaceString: cabalDep.replaceString.trim(), + autoReplaceStringTemplate: '{{{depName}}} {{{newValue}}}', + }; + deps.push(dep); + } + current = current.slice(maybeContent.lengthProcessed); + } + return { deps }; +} + +export function getRangeStrategy({ + rangeStrategy, +}: RangeConfig): RangeStrategy { + if (rangeStrategy === 'auto') { + return 'widen'; + } + return rangeStrategy; +} diff --git a/lib/modules/manager/haskell-cabal/readme.md b/lib/modules/manager/haskell-cabal/readme.md new file mode 100644 index 00000000000000..e2c90a6fac4fd3 --- /dev/null +++ b/lib/modules/manager/haskell-cabal/readme.md @@ -0,0 +1,10 @@ +Supports dependency extraction from `build-depends` fields in [Cabal package description files](https://cabal.readthedocs.io/en/3.12/cabal-package-description-file.html#pkg-field-build-depends). +They use the extension `.cabal`, and are used with the [Haskell programming language](https://www.haskell.org/). + +Limitations: + +- The dependencies of all components are mushed together in one big list. +- Fields like `pkgconfig-depends` and `build-tool-depends` are not handled. +- The default PVP versioning is [subject to limitations](../../versioning/pvp/index.md). + +If you need to change the versioning format, read the [versioning](../../versioning/index.md) documentation to learn more. diff --git a/lib/modules/manager/helm-values/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/helm-values/__snapshots__/extract.spec.ts.snap index cc2f4c66dfb924..22ab3d7bab40ef 100644 --- a/lib/modules/manager/helm-values/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/helm-values/__snapshots__/extract.spec.ts.snap @@ -9,6 +9,7 @@ exports[`modules/manager/helm-values/extract extractPackageFile() extracts from "currentValue": "1.18-alpine", "datasource": "docker", "depName": "docker.io/library/nginx", + "packageName": "docker.io/library/nginx", "replaceString": "docker.io/library/nginx:1.18-alpine", }, { @@ -17,6 +18,7 @@ exports[`modules/manager/helm-values/extract extractPackageFile() extracts from "currentValue": "11.6.0-debian-9-r0", "datasource": "docker", "depName": "bitnami/postgresql", + "packageName": "bitnami/postgresql", "replaceString": "11.6.0-debian-9-r0", "versioning": "docker", }, @@ -26,6 +28,7 @@ exports[`modules/manager/helm-values/extract extractPackageFile() extracts from "currentValue": "0.7.0-debian-9-r12", "datasource": "docker", "depName": "docker.io/bitnami/postgres-exporter", + "packageName": "docker.io/bitnami/postgres-exporter", "replaceString": "0.7.0-debian-9-r12", "versioning": "docker", }, @@ -35,6 +38,7 @@ exports[`modules/manager/helm-values/extract extractPackageFile() extracts from "currentValue": "11.5.0-debian-9-r0", "datasource": "docker", "depName": "docker.io/bitnami/postgresql", + "packageName": "docker.io/bitnami/postgresql", "replaceString": "11.5.0-debian-9-r0@sha256:4762726f1471ef048dd807afdc0e19265e95ffdcc7cb4a34891f680290022809", "versioning": "docker", }, @@ -44,6 +48,7 @@ exports[`modules/manager/helm-values/extract extractPackageFile() extracts from "currentValue": "2.1.3-debian-10-r38", "datasource": "docker", "depName": "docker.io/bitnami/harbor-core", + "packageName": "docker.io/bitnami/harbor-core", "replaceString": "2.1.3-debian-10-r38", "versioning": "docker", }, @@ -60,6 +65,7 @@ exports[`modules/manager/helm-values/extract extractPackageFile() extracts from "currentValue": "1.16.1", "datasource": "docker", "depName": "nginx", + "packageName": "nginx", "replaceString": "1.16.1", "versioning": "docker", }, diff --git a/lib/modules/manager/helm-values/extract.spec.ts b/lib/modules/manager/helm-values/extract.spec.ts index abab66d76f44e0..7a8ba8c048f0fe 100644 --- a/lib/modules/manager/helm-values/extract.spec.ts +++ b/lib/modules/manager/helm-values/extract.spec.ts @@ -95,13 +95,15 @@ describe('modules/manager/helm-values/extract', () => { deps: [ { currentValue: 'v0.13.10', - depName: 'registry.internal/mirror/quay.io/metallb/controller', + depName: 'quay.io/metallb/controller', + packageName: 'registry.internal/mirror/quay.io/metallb/controller', datasource: 'docker', versioning: 'docker', }, { currentValue: 'v0.13.10', - depName: 'registry.internal/mirror/quay.io/metallb/speaker', + depName: 'quay.io/metallb/speaker', + packageName: '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 59e465c0f0000e..2987b3ca1297e3 100644 --- a/lib/modules/manager/helm-values/extract.ts +++ b/lib/modules/manager/helm-values/extract.ts @@ -17,13 +17,9 @@ function getHelmDep( registry: string, repository: string, tag: string, - config: ExtractConfig, + registryAliases: Record | undefined, ): PackageDependency { - const dep = getDep( - `${registry}${repository}:${tag}`, - false, - config.registryAliases, - ); + const dep = getDep(`${registry}${repository}:${tag}`, false, registryAliases); dep.replaceString = tag; dep.versioning = dockerVersioning; dep.autoReplaceStringTemplate = @@ -36,11 +32,17 @@ function getHelmDep( * * @param parsedContent */ -function findDependencies( +export function findDependencies( parsedContent: Record | HelmDockerImageDependency, - packageDependencies: Array, - config: ExtractConfig, -): Array { + registryAliases: Record | undefined, +): PackageDependency[] { + return findDependenciesInternal(parsedContent, [], registryAliases); +} +export function findDependenciesInternal( + parsedContent: Record | HelmDockerImageDependency, + packageDependencies: PackageDependency[], + registryAliases: Record | undefined, +): PackageDependency[] { if (!parsedContent || typeof parsedContent !== 'object') { return packageDependencies; } @@ -53,14 +55,16 @@ function findDependencies( registry = registry ? `${registry}/` : ''; const repository = String(currentItem.repository); const tag = `${currentItem.tag ?? currentItem.version}`; - packageDependencies.push(getHelmDep(registry, repository, tag, config)); + packageDependencies.push( + getHelmDep(registry, repository, tag, registryAliases), + ); } else if (matchesHelmValuesInlineImage(key, value)) { - packageDependencies.push(getDep(value, true, config.registryAliases)); + packageDependencies.push(getDep(value, true, registryAliases)); } else { - findDependencies( + findDependenciesInternal( value as Record, packageDependencies, - config, + registryAliases, ); } }); @@ -86,7 +90,7 @@ export function extractPackageFile( const deps: PackageDependency>[] = []; for (const con of parsedContent) { - deps.push(...findDependencies(con, [], config)); + deps.push(...findDependencies(con, config.registryAliases)); } if (deps.length) { diff --git a/lib/modules/manager/index.spec.ts b/lib/modules/manager/index.spec.ts index 1c635b82ff6dfe..96411f363a0836 100644 --- a/lib/modules/manager/index.spec.ts +++ b/lib/modules/manager/index.spec.ts @@ -170,7 +170,7 @@ describe('modules/manager/index', () => { }); describe('getRangeStrategy', () => { - it('returns null', () => { + it('returns null for a unknown manager', () => { manager.getManagers().set('dummy', { defaultConfig: {}, supportedDatasources: [], @@ -180,6 +180,37 @@ describe('modules/manager/index', () => { ).toBeNull(); }); + it('returns null for a undefined manager', () => { + manager.getManagers().set('dummy', { + defaultConfig: {}, + supportedDatasources: [], + }); + expect(manager.getRangeStrategy({ rangeStrategy: 'auto' })).toBeNull(); + }); + + it('returns non-null for a custom manager', () => { + customManager.getCustomManagers().set('dummy', { + defaultConfig: {}, + supportedDatasources: [], + }); + expect( + manager.getRangeStrategy({ manager: 'dummy', rangeStrategy: 'auto' }), + ).not.toBeNull(); + }); + + it('handles custom managers', () => { + customManager.getCustomManagers().set('dummy', { + defaultConfig: {}, + supportedDatasources: [], + // For completeness. Custom managers are configured in json and can not + // provide a range strategy (yet) but the interface allows for it. + getRangeStrategy: () => 'bump', + }); + expect( + manager.getRangeStrategy({ manager: 'dummy', rangeStrategy: 'auto' }), + ).toBe('bump'); + }); + it('returns non-null', () => { manager.getManagers().set('dummy', { defaultConfig: {}, diff --git a/lib/modules/manager/index.ts b/lib/modules/manager/index.ts index e4f9eace2969a3..38a80e5c218784 100644 --- a/lib/modules/manager/index.ts +++ b/lib/modules/manager/index.ts @@ -78,10 +78,13 @@ export function extractPackageFile( export function getRangeStrategy(config: RangeConfig): RangeStrategy | null { const { manager, rangeStrategy } = config; - if (!manager || !managers.has(manager)) { + if (!manager) { + return null; + } + const m = managers.get(manager) ?? customManagers.get(manager); + if (!m) { return null; } - const m = managers.get(manager)!; if (m.getRangeStrategy) { // Use manager's own function if it exists const managerRangeStrategy = m.getRangeStrategy(config); diff --git a/lib/modules/manager/kubernetes/extract.spec.ts b/lib/modules/manager/kubernetes/extract.spec.ts index 6a4f5403f20d3d..412a7894ca51d2 100644 --- a/lib/modules/manager/kubernetes/extract.spec.ts +++ b/lib/modules/manager/kubernetes/extract.spec.ts @@ -28,6 +28,7 @@ describe('modules/manager/kubernetes/extract', () => { currentValue: '1.7.9', datasource: 'docker', depName: 'nginx', + packageName: 'nginx', replaceString: 'nginx:1.7.9', }, { @@ -37,6 +38,7 @@ describe('modules/manager/kubernetes/extract', () => { currentValue: '1.22.1', datasource: 'docker', depName: 'nginx', + packageName: 'nginx', replaceString: 'nginx:1.22.1', }, { @@ -46,6 +48,7 @@ describe('modules/manager/kubernetes/extract', () => { currentValue: 'v1.11.1', datasource: 'docker', depName: 'k8s.gcr.io/kube-proxy-amd64', + packageName: 'k8s.gcr.io/kube-proxy-amd64', replaceString: 'k8s.gcr.io/kube-proxy-amd64:v1.11.1', }, { @@ -77,6 +80,7 @@ describe('modules/manager/kubernetes/extract', () => { currentValue: 'v2.1.0', datasource: 'docker', depName: 'quay.io/external_storage/local-volume-provisioner', + packageName: 'quay.io/external_storage/local-volume-provisioner', replaceString: 'quay.io/external_storage/local-volume-provisioner:v2.1.0', }, @@ -115,7 +119,8 @@ kind: ConfigMap currentDigest: undefined, currentValue: '0.0.1', datasource: 'docker', - depName: 'my-quay-mirror.registry.com/node', + depName: 'quay.io/node', + packageName: 'my-quay-mirror.registry.com/node', replaceString: 'quay.io/node:0.0.1', }, ], @@ -137,6 +142,7 @@ kind: ConfigMap currentValue: '0.0.1', datasource: 'docker', depName: 'quay.io/node', + packageName: 'quay.io/node', replaceString: 'quay.io/node:0.0.1', }, ], @@ -158,7 +164,8 @@ kind: ConfigMap currentDigest: undefined, currentValue: '0.0.1', datasource: 'docker', - depName: 'my-quay-mirror.registry.com/node', + depName: 'quay.io/node', + packageName: 'my-quay-mirror.registry.com/node', replaceString: 'quay.io/node:0.0.1', }, ], @@ -180,6 +187,7 @@ kind: ConfigMap currentValue: undefined, datasource: 'docker', depName: 'busybox', + packageName: 'busybox', replaceString: 'busybox', }, ], diff --git a/lib/modules/manager/kustomize/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/kustomize/__snapshots__/extract.spec.ts.snap index 210d7ccf638dd8..aae5dc9b65952e 100644 --- a/lib/modules/manager/kustomize/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/kustomize/__snapshots__/extract.spec.ts.snap @@ -9,6 +9,7 @@ exports[`modules/manager/kustomize/extract extractPackageFile() extracts from di "datasource": "docker", "depName": "postgres", "depType": "Kustomization", + "packageName": "postgres", "replaceString": "sha256:b0cfe264cb1143c7c660ddfd5c482464997d62d6bc9f97f8fdf3deefce881a8c", }, { @@ -17,6 +18,7 @@ exports[`modules/manager/kustomize/extract extractPackageFile() extracts from di "datasource": "docker", "depName": "postgres", "depType": "Kustomization", + "packageName": "postgres", "replaceString": "sha256:b0cfe264cb1143c7c660ddfd5c482464997d62d6bc9f97f8fdf3deefce881a8c", }, { @@ -52,6 +54,7 @@ exports[`modules/manager/kustomize/extract extractPackageFile() extracts from ne "datasource": "docker", "depName": "postgres", "depType": "Kustomization", + "packageName": "postgres", "replaceString": "11", }, { @@ -61,6 +64,7 @@ exports[`modules/manager/kustomize/extract extractPackageFile() extracts from ne "datasource": "docker", "depName": "postgres", "depType": "Kustomization", + "packageName": "postgres", "replaceString": "11@sha256:b0cfe264cb1143c7c660ddfd5c482464997d62d6bc9f97f8fdf3deefce881a8c", }, { @@ -118,6 +122,7 @@ exports[`modules/manager/kustomize/extract extractPackageFile() extracts newName "datasource": "docker", "depName": "awesome/postgres", "depType": "Kustomization", + "packageName": "awesome/postgres", "replaceString": "awesome/postgres:11@sha256:b0cfe264cb1143c7c660ddfd5c482464997d62d6bc9f97f8fdf3deefce881a8c", }, { @@ -126,6 +131,7 @@ exports[`modules/manager/kustomize/extract extractPackageFile() extracts newName "datasource": "docker", "depName": "awesome/postgres", "depType": "Kustomization", + "packageName": "awesome/postgres", "replaceString": "awesome/postgres:11", }, { @@ -134,6 +140,7 @@ exports[`modules/manager/kustomize/extract extractPackageFile() extracts newName "datasource": "docker", "depName": "awesome/postgres", "depType": "Kustomization", + "packageName": "awesome/postgres", "replaceString": "awesome/postgres@sha256:b0cfe264cb1143c7c660ddfd5c482464997d62d6bc9f97f8fdf3deefce881a8c", }, ], @@ -231,6 +238,7 @@ exports[`modules/manager/kustomize/extract extractPackageFile() should extract d "datasource": "docker", "depName": "node", "depType": "Component", + "packageName": "node", "replaceString": "v0.1.0", }, ] @@ -245,6 +253,7 @@ exports[`modules/manager/kustomize/extract extractPackageFile() should extract o "datasource": "docker", "depName": "node", "depType": "Kustomization", + "packageName": "node", "replaceString": "v0.1.0", }, { @@ -254,6 +263,7 @@ exports[`modules/manager/kustomize/extract extractPackageFile() should extract o "datasource": "docker", "depName": "group/instance", "depType": "Kustomization", + "packageName": "group/instance", "replaceString": "v0.0.1", }, { @@ -263,6 +273,7 @@ exports[`modules/manager/kustomize/extract extractPackageFile() should extract o "datasource": "docker", "depName": "quay.io/test/repo", "depType": "Kustomization", + "packageName": "quay.io/test/repo", "replaceString": "v0.0.2", }, { @@ -272,6 +283,7 @@ exports[`modules/manager/kustomize/extract extractPackageFile() should extract o "datasource": "docker", "depName": "gitlab.com/org/suborg/image", "depType": "Kustomization", + "packageName": "gitlab.com/org/suborg/image", "replaceString": "v0.0.3", }, { @@ -281,6 +293,7 @@ exports[`modules/manager/kustomize/extract extractPackageFile() should extract o "datasource": "docker", "depName": "but.this.lives.on.local/private-registry", "depType": "Kustomization", + "packageName": "but.this.lives.on.local/private-registry", "replaceString": "v0.0.4", }, { diff --git a/lib/modules/manager/kustomize/extract.spec.ts b/lib/modules/manager/kustomize/extract.spec.ts index 4465cd9aee81c2..3e70d18e0cffff 100644 --- a/lib/modules/manager/kustomize/extract.spec.ts +++ b/lib/modules/manager/kustomize/extract.spec.ts @@ -267,6 +267,7 @@ describe('modules/manager/kustomize/extract', () => { datasource: DockerDatasource.id, replaceString: 'v1.0.0', depName: 'node', + packageName: 'node', }; const pkg = extractImage({ name: sample.depName, @@ -284,6 +285,7 @@ describe('modules/manager/kustomize/extract', () => { datasource: DockerDatasource.id, replaceString: 'v1.0.0', depName: 'test/node', + packageName: 'test/node', }; const pkg = extractImage({ name: sample.depName, @@ -301,6 +303,7 @@ describe('modules/manager/kustomize/extract', () => { datasource: DockerDatasource.id, replaceString: 'v1.0.0', depName: 'quay.io/repo/image', + packageName: 'quay.io/repo/image', }; const pkg = extractImage({ name: sample.depName, @@ -318,6 +321,7 @@ describe('modules/manager/kustomize/extract', () => { datasource: DockerDatasource.id, replaceString: 'v1.0.0', depName: 'localhost:5000/repo/image', + packageName: 'localhost:5000/repo/image', }; const pkg = extractImage({ name: sample.depName, @@ -335,6 +339,7 @@ describe('modules/manager/kustomize/extract', () => { replaceString: 'v1.0.0', datasource: DockerDatasource.id, depName: 'localhost:5000/repo/image/service', + packageName: 'localhost:5000/repo/image/service', }; const pkg = extractImage({ name: sample.depName, @@ -351,7 +356,8 @@ describe('modules/manager/kustomize/extract', () => { currentValue: 'v1.0.0', replaceString: 'v1.0.0', datasource: DockerDatasource.id, - depName: 'docker.io/image/service', + depName: 'localhost:5000/repo/image/service', + packageName: 'docker.io/image/service', }; const pkg = extractImage( { diff --git a/lib/modules/manager/kustomize/extract.ts b/lib/modules/manager/kustomize/extract.ts index 9051607e1ad46d..815917e06ea4fd 100644 --- a/lib/modules/manager/kustomize/extract.ts +++ b/lib/modules/manager/kustomize/extract.ts @@ -167,7 +167,6 @@ export function extractHelmChart( return { ...dep, depName: helmChart.name, - packageName: dep.depName, // https://github.com/helm/helm/issues/10312 // https://github.com/helm/helm/issues/10678 pinDigests: false, diff --git a/lib/modules/manager/leiningen/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/leiningen/__snapshots__/extract.spec.ts.snap index 53d474d9490a58..147031d8545874 100644 --- a/lib/modules/manager/leiningen/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/leiningen/__snapshots__/extract.spec.ts.snap @@ -116,13 +116,13 @@ exports[`modules/manager/leiningen/extract extractPackageFile 1`] = ` "datasource": "clojure", "depName": "clj-stacktrace:clj-stacktrace", "depType": "dependencies", - "groupName": "clj-stacktrace-version", "registryUrls": [ "https://download.java.net/maven/2", "https://oss.sonatype.org/content/repositories/releases", "https://blueant.com/archiva/snapshots", "https://blueant.com/archiva/internal", ], + "sharedVariableName": "clj-stacktrace-version", }, { "currentValue": "0.12.0", diff --git a/lib/modules/manager/leiningen/extract.spec.ts b/lib/modules/manager/leiningen/extract.spec.ts index 76e3d765e03d4e..867a2a5d964dcd 100644 --- a/lib/modules/manager/leiningen/extract.spec.ts +++ b/lib/modules/manager/leiningen/extract.spec.ts @@ -38,7 +38,7 @@ describe('modules/manager/leiningen/extract', () => { datasource: ClojureDatasource.id, depName: 'foo:bar', currentValue: '1.2.3', - groupName: 'baz', + sharedVariableName: 'baz', }, ]); expect( @@ -86,7 +86,7 @@ describe('modules/manager/leiningen/extract', () => { { depName: 'clj-stacktrace:clj-stacktrace', currentValue: '0.2.4', - groupName: 'clj-stacktrace-version', + sharedVariableName: 'clj-stacktrace-version', }, { depName: 'clj-time:clj-time', diff --git a/lib/modules/manager/leiningen/extract.ts b/lib/modules/manager/leiningen/extract.ts index 680b5f14cb025f..54ede6bd46fe68 100644 --- a/lib/modules/manager/leiningen/extract.ts +++ b/lib/modules/manager/leiningen/extract.ts @@ -58,7 +58,7 @@ export function extractFromVectors( datasource: ClojureDatasource.id, depName, currentValue, - groupName: varName, + sharedVariableName: varName, }); } } else { diff --git a/lib/modules/manager/maven/extract.spec.ts b/lib/modules/manager/maven/extract.spec.ts index 3efc9d99aea9fa..f564c877d543a3 100644 --- a/lib/modules/manager/maven/extract.spec.ts +++ b/lib/modules/manager/maven/extract.spec.ts @@ -476,7 +476,7 @@ describe('modules/manager/maven/extract', () => { depType: 'compile', editFile: 'parent.pom.xml', fileReplacePosition: 470, - groupName: 'quuxVersion', + sharedVariableName: 'quuxVersion', registryUrls: [ 'http://example.com/', 'http://example.com/nexus/xyz', @@ -697,12 +697,12 @@ describe('modules/manager/maven/extract', () => { { depName: 'org.example:quux', currentValue: '1.2.3.4', - groupName: 'quuxVersion', + sharedVariableName: 'quuxVersion', }, { depName: 'org.example:quux-test', currentValue: '1.2.3.4', - groupName: 'quuxVersion', + sharedVariableName: 'quuxVersion', }, { depName: 'org.example:quuz', diff --git a/lib/modules/manager/maven/extract.ts b/lib/modules/manager/maven/extract.ts index ef06fe3911228c..939e31e25c1e8a 100644 --- a/lib/modules/manager/maven/extract.ts +++ b/lib/modules/manager/maven/extract.ts @@ -224,7 +224,7 @@ function applyPropsInternal( let fileReplacePosition = dep.fileReplacePosition; let propSource = dep.propSource; - let groupName: string | null = null; + let sharedVariableName: string | null = null; const currentValue = dep.currentValue!.replace( regEx(/^\${[^}]*?}$/), (substr) => { @@ -232,8 +232,8 @@ function applyPropsInternal( // TODO: wrong types here, props is already `MavenProp` const propValue = (props as any)[propKey] as MavenProp; if (propValue) { - if (!groupName) { - groupName = propKey; + if (!sharedVariableName) { + sharedVariableName = propKey; } fileReplacePosition = propValue.fileReplacePosition; propSource = @@ -261,8 +261,8 @@ function applyPropsInternal( currentValue, }; - if (groupName) { - result.groupName = groupName; + if (sharedVariableName) { + result.sharedVariableName = sharedVariableName; } if (propSource && depPackageFile !== propSource) { diff --git a/lib/modules/manager/maven/update.spec.ts b/lib/modules/manager/maven/update.spec.ts index a6f9a5717a361e..91b06eac3e2bdc 100644 --- a/lib/modules/manager/maven/update.spec.ts +++ b/lib/modules/manager/maven/update.spec.ts @@ -1,4 +1,5 @@ // TODO #22198 +import { codeBlock } from 'common-tags'; import { XmlDocument } from 'xmldoc'; import { Fixtures } from '../../../../test/fixtures'; import { bumpPackageVersion, updateDependency } from './update'; @@ -10,12 +11,135 @@ const prereleaseContent = Fixtures.get(`prerelease.pom.xml`); describe('modules/manager/maven/update', () => { describe('updateDependency', () => { - it('should return null for replacement', () => { + it('should update version', () => { const res = updateDependency({ - fileContent: '', - upgrade: { updateType: 'replacement' }, + fileContent: simpleContent, + upgrade: { + updateType: 'patch', + depName: 'org.example:foo', + currentValue: '0.0.1', + fileReplacePosition: 905, + newValue: '0.0.2', + }, }); - expect(res).toBeNull(); + + const project = new XmlDocument(res!); + expect( + project.valueWithPath( + 'dependencyManagement.dependencies.dependency.version', + ), + ).toBe('0.0.2'); + }); + + it('should do simple replacement', () => { + const res = updateDependency({ + fileContent: simpleContent, + upgrade: { + updateType: 'replacement', + depName: 'org.example:foo', + currentValue: '0.0.1', + fileReplacePosition: 905, + newName: 'org.example.new:foo', + newValue: '0.0.1', + }, + }); + + const project = new XmlDocument(res!); + expect( + project.valueWithPath( + 'dependencyManagement.dependencies.dependency.groupId', + ), + ).toBe('org.example.new'); + }); + + it('should do full replacement', () => { + const res = updateDependency({ + fileContent: simpleContent, + upgrade: { + updateType: 'replacement', + depName: 'org.example:foo', + currentValue: '0.0.1', + fileReplacePosition: 905, + newName: 'org.example.new:bar', + newValue: '0.0.2', + }, + }); + + const project = new XmlDocument(res!); + expect( + project.valueWithPath( + 'dependencyManagement.dependencies.dependency.groupId', + ), + ).toBe('org.example.new'); + expect( + project.valueWithPath( + 'dependencyManagement.dependencies.dependency.artifactId', + ), + ).toBe('bar'); + expect( + project.valueWithPath( + 'dependencyManagement.dependencies.dependency.version', + ), + ).toBe('0.0.2'); + }); + + it('should do replacement if version is first', () => { + const res = updateDependency({ + fileContent: codeBlock` + + + + + 0.0.1 + foo + org.example + + + + + `, + upgrade: { + updateType: 'replacement', + depName: 'org.example:foo', + currentValue: '0.0.1', + fileReplacePosition: 132, + newName: 'org.example.new:bar', + newValue: '0.0.1', + }, + }); + + const project = new XmlDocument(res!); + expect( + project.valueWithPath( + 'dependencyManagement.dependencies.dependency.groupId', + ), + ).toBe('org.example.new'); + expect( + project.valueWithPath( + 'dependencyManagement.dependencies.dependency.artifactId', + ), + ).toBe('bar'); + expect( + project.valueWithPath( + 'dependencyManagement.dependencies.dependency.version', + ), + ).toBe('0.0.1'); + }); + + it('should ignore replacement if name does not match', () => { + const res = updateDependency({ + fileContent: simpleContent, + upgrade: { + updateType: 'replacement', + depName: 'org.example.old:bar', + currentValue: '0.0.1', + fileReplacePosition: 905, + newName: 'org.example:foo', + newValue: '0.0.1', + }, + }); + + expect(res).toBe(simpleContent); }); }); diff --git a/lib/modules/manager/maven/update.ts b/lib/modules/manager/maven/update.ts index b22261c0a85162..3c4c87386d21dd 100644 --- a/lib/modules/manager/maven/update.ts +++ b/lib/modules/manager/maven/update.ts @@ -15,17 +15,57 @@ export function updateAtPosition( upgrade: Upgrade, endingAnchor: string, ): string | null { - const { depName, currentValue, newValue, fileReplacePosition } = upgrade; - const leftPart = fileContent.slice(0, fileReplacePosition); + const { depName, newName, currentValue, newValue, fileReplacePosition } = + upgrade; + let leftPart = fileContent.slice(0, fileReplacePosition); const rightPart = fileContent.slice(fileReplacePosition); const versionClosePosition = rightPart.indexOf(endingAnchor); - const restPart = rightPart.slice(versionClosePosition); + let restPart = rightPart.slice(versionClosePosition); const versionPart = rightPart.slice(0, versionClosePosition); const version = versionPart.trim(); - if (version === newValue) { + if (newName) { + const blockStart = Math.max( + leftPart.lastIndexOf(' 0) { + leftBlock = updateValue(leftBlock, 'groupId', groupId, newGroupId); + } else { + rightBlock = updateValue(rightBlock, 'groupId', groupId, newGroupId); + } + if (leftBlock.indexOf(' 0) { + leftBlock = updateValue( + leftBlock, + 'artifactId', + artifactId, + newArtifactId, + ); + } else { + rightBlock = updateValue( + rightBlock, + 'artifactId', + artifactId, + newArtifactId, + ); + } + leftPart = leftPart.slice(0, blockStart) + leftBlock; + restPart = rightBlock + restPart.slice(blockEnd); + } else if (version === newValue) { return fileContent; } - if (version === currentValue || upgrade.groupName) { + if (version === currentValue || upgrade.sharedVariableName) { // TODO: validate newValue (#22198) const replacedPart = versionPart.replace(version, newValue!); return leftPart + replacedPart + restPart; @@ -38,10 +78,6 @@ export function updateDependency({ fileContent, upgrade, }: UpdateDependencyConfig): string | null { - if (upgrade.updateType === 'replacement') { - logger.warn('maven manager does not support replacement updates yet'); - return null; - } const offset = fileContent.indexOf('<'); const spaces = fileContent.slice(0, offset); const restContent = fileContent.slice(offset); @@ -141,3 +177,24 @@ function isSnapshot( const lastPart = prerelease?.at(-1); return is.string(lastPart) && lastPart.endsWith('SNAPSHOT'); } + +function updateValue( + content: string, + nodeName: string, + oldValue: string, + newValue: string, +): string { + const elementStart = content.indexOf('<' + nodeName); + const start = content.indexOf('>', elementStart) + 1; + const end = content.indexOf(' 1.0", organization: "acme"}, {:also_secret, "~> 1.0", only: [:dev, :test], organization: "acme", runtime: false}, {:metrics, ">0.2.0 and <=1.0.0"}, - {:jason, ">= 1.0.0"}, + {:jason, ">= 1.0.0", only: :prod}, {:hackney, "~> 1.0", optional: true}, - {:hammer_backend_redis, "~> 6.1"}, + {:hammer_backend_redis, "~> 6.1", only: [:dev, :prod, :test]}, {:castore, "== 1.0.10"}, {:gun, "~> 2.0.0", hex: "grpc_gun"}, {:another_gun, "~> 0.4.0", hex: :raygun}, + {:credo, "~> 1.7", only: + [:test, + # prod, + :dev], + runtime: false}, + {:floki, "== 0.37.0", only: :test}, ] end end diff --git a/lib/modules/manager/mix/__fixtures__/mix.lock b/lib/modules/manager/mix/__fixtures__/mix.lock index d90b44ee2d1c38..106d497cd6b91e 100644 --- a/lib/modules/manager/mix/__fixtures__/mix.lock +++ b/lib/modules/manager/mix/__fixtures__/mix.lock @@ -1,13 +1,17 @@ %{ "another_gun": {:hex, :raygun, "0.4.0", "7744e99dd695f61e78ad5e047cce0affb3edfc6f93a92278598ab553b9c5091f", [:mix], [{:httpoison, "~> 0.8 or ~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:plug, "~> 1.1", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "eee4b891e6e65c6a4b15386dc7b7a72b717f3c123cc0012cfd19e8f2ab21116d"}, + "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castore": {:hex, :castore, "1.0.10", "43bbeeac820f16c89f79721af1b3e092399b3a1ecc8df1a472738fd853574911", [:mix], [], "hexpm", "1b0b7ea14d889d9ea21202c43a4fa015eb913021cb535e8ed91946f4b77a8848"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "cowboy": {:git, "https://github.com/ninenines/cowboy.git", "0c2e2224e372f01e6cf51a8e12d4856edb4cb8ac", [tag: "0.6.0"]}, "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, + "credo": {:hex, :credo, "1.7.10", "6e64fe59be8da5e30a1b96273b247b5cf1cc9e336b5fd66302a64b25749ad44d", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "71fbc9a6b8be21d993deca85bf151df023a3097b01e09a2809d460348561d8cd"}, "decimal": {:hex, :decimal, "1.9.0", "83e8daf59631d632b171faabafb4a9f4242c514b0a06ba3df493951c08f64d07", [:mix], [], "hexpm", "b1f2343568eed6928f3e751cf2dffde95bfaa19dd95d09e8a9ea92ccfd6f7d85"}, "ecto": {:git, "https://github.com/elixir-ecto/ecto.git", "795036d997c7503b21fb64d6bf1a89b83c44f2b5", [ref: "795036d997c7503b21fb64d6bf1a89b83c44f2b5"]}, "secret": {:hex, :secret, "1.5.0", "344dbbf6610d205760ec37e2848bff2aab5a2de182bb5cdaa72cc2fd19d74535", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "19c205c8de0e2e5817f2250100281c58e717cb11ff1bb410bf661ee78c24e79b"}, "also_secret": {:hex, :also_secret, "1.3.4", "344dbbf6610d205760ec37e2848bff2aab5a2de182bb5cdaa72cc2fd19d74535", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "19c205c8de0e2e5817f2250100281c58e717cb11ff1bb410bf661ee78c24e79b"}, + "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, + "floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"}, "gun": {:hex, :grpc_gun, "2.0.1", "221b792df3a93e8fead96f697cbaf920120deacced85c6cd3329d2e67f0871f8", [:rebar3], [{:cowlib, "~> 2.11", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "795a65eb9d0ba16697e6b0e1886009ce024799e43bb42753f0c59b029f592831"}, "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, "hammer": {:hex, :hammer, "6.2.1", "5ae9c33e3dceaeb42de0db46bf505bd9c35f259c8defb03390cd7556fea67ee2", [:mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "b9476d0c13883d2dc0cc72e786bac6ac28911fba7cc2e04b70ce6a6d9c4b2bdc"}, diff --git a/lib/modules/manager/mix/extract.spec.ts b/lib/modules/manager/mix/extract.spec.ts index 3663073aabc20c..a20ea0327fd715 100644 --- a/lib/modules/manager/mix/extract.spec.ts +++ b/lib/modules/manager/mix/extract.spec.ts @@ -20,12 +20,14 @@ describe('modules/manager/mix/extract', () => { currentValue: '~> 0.8.1', datasource: 'hex', depName: 'postgrex', + depType: 'prod', packageName: 'postgrex', }, { currentValue: '<1.7.0 or ~>1.7.1', datasource: 'hex', depName: 'ranch', + depType: 'prod', packageName: 'ranch', }, { @@ -33,6 +35,7 @@ describe('modules/manager/mix/extract', () => { currentValue: '0.6.0', datasource: 'github-tags', depName: 'cowboy', + depType: 'prod', packageName: 'ninenines/cowboy', }, { @@ -40,6 +43,7 @@ describe('modules/manager/mix/extract', () => { currentValue: 'main', datasource: 'git-tags', depName: 'phoenix', + depType: 'prod', packageName: 'https://github.com/phoenixframework/phoenix.git', }, { @@ -47,42 +51,49 @@ describe('modules/manager/mix/extract', () => { currentValue: undefined, datasource: 'github-tags', depName: 'ecto', + depType: 'prod', packageName: 'elixir-ecto/ecto', }, { currentValue: '~> 1.0', datasource: 'hex', depName: 'secret', + depType: 'prod', packageName: 'secret:acme', }, { currentValue: '~> 1.0', datasource: 'hex', depName: 'also_secret', + depType: 'dev', packageName: 'also_secret:acme', }, { currentValue: '>0.2.0 and <=1.0.0', datasource: 'hex', depName: 'metrics', + depType: 'prod', packageName: 'metrics', }, { currentValue: '>= 1.0.0', datasource: 'hex', depName: 'jason', + depType: 'prod', packageName: 'jason', }, { currentValue: '~> 1.0', datasource: 'hex', depName: 'hackney', + depType: 'prod', packageName: 'hackney', }, { currentValue: '~> 6.1', datasource: 'hex', depName: 'hammer_backend_redis', + depType: 'prod', packageName: 'hammer_backend_redis', }, { @@ -90,20 +101,38 @@ describe('modules/manager/mix/extract', () => { currentVersion: '1.0.10', datasource: 'hex', depName: 'castore', + depType: 'prod', packageName: 'castore', }, { currentValue: '~> 2.0.0', datasource: 'hex', depName: 'gun', + depType: 'prod', packageName: 'grpc_gun', }, { currentValue: '~> 0.4.0', datasource: 'hex', depName: 'another_gun', + depType: 'prod', packageName: 'raygun', }, + { + currentValue: '~> 1.7', + datasource: 'hex', + depName: 'credo', + depType: 'dev', + packageName: 'credo', + }, + { + currentValue: '== 0.37.0', + currentVersion: '0.37.0', + datasource: 'hex', + depName: 'floki', + depType: 'dev', + packageName: 'floki', + }, ]); }); @@ -116,6 +145,7 @@ describe('modules/manager/mix/extract', () => { currentValue: '~> 0.8.1', datasource: 'hex', depName: 'postgrex', + depType: 'prod', packageName: 'postgrex', lockedVersion: '0.8.4', }, @@ -123,6 +153,7 @@ describe('modules/manager/mix/extract', () => { currentValue: '<1.7.0 or ~>1.7.1', datasource: 'hex', depName: 'ranch', + depType: 'prod', packageName: 'ranch', lockedVersion: '1.7.1', }, @@ -131,6 +162,7 @@ describe('modules/manager/mix/extract', () => { currentValue: '0.6.0', datasource: 'github-tags', depName: 'cowboy', + depType: 'prod', packageName: 'ninenines/cowboy', lockedVersion: '0.6.0', }, @@ -139,6 +171,7 @@ describe('modules/manager/mix/extract', () => { currentValue: 'main', datasource: 'git-tags', depName: 'phoenix', + depType: 'prod', packageName: 'https://github.com/phoenixframework/phoenix.git', lockedVersion: undefined, }, @@ -147,6 +180,7 @@ describe('modules/manager/mix/extract', () => { currentValue: undefined, datasource: 'github-tags', depName: 'ecto', + depType: 'prod', packageName: 'elixir-ecto/ecto', lockedVersion: undefined, }, @@ -154,6 +188,7 @@ describe('modules/manager/mix/extract', () => { currentValue: '~> 1.0', datasource: 'hex', depName: 'secret', + depType: 'prod', packageName: 'secret:acme', lockedVersion: '1.5.0', }, @@ -161,6 +196,7 @@ describe('modules/manager/mix/extract', () => { currentValue: '~> 1.0', datasource: 'hex', depName: 'also_secret', + depType: 'dev', packageName: 'also_secret:acme', lockedVersion: '1.3.4', }, @@ -168,6 +204,7 @@ describe('modules/manager/mix/extract', () => { currentValue: '>0.2.0 and <=1.0.0', datasource: 'hex', depName: 'metrics', + depType: 'prod', packageName: 'metrics', lockedVersion: '1.0.0', }, @@ -175,6 +212,7 @@ describe('modules/manager/mix/extract', () => { currentValue: '>= 1.0.0', datasource: 'hex', depName: 'jason', + depType: 'prod', packageName: 'jason', lockedVersion: '1.4.4', }, @@ -182,6 +220,7 @@ describe('modules/manager/mix/extract', () => { currentValue: '~> 1.0', datasource: 'hex', depName: 'hackney', + depType: 'prod', packageName: 'hackney', lockedVersion: '1.20.1', }, @@ -189,6 +228,7 @@ describe('modules/manager/mix/extract', () => { currentValue: '~> 6.1', datasource: 'hex', depName: 'hammer_backend_redis', + depType: 'prod', packageName: 'hammer_backend_redis', lockedVersion: '6.2.0', }, @@ -197,6 +237,7 @@ describe('modules/manager/mix/extract', () => { currentVersion: '1.0.10', datasource: 'hex', depName: 'castore', + depType: 'prod', packageName: 'castore', lockedVersion: '1.0.10', }, @@ -204,6 +245,7 @@ describe('modules/manager/mix/extract', () => { currentValue: '~> 2.0.0', datasource: 'hex', depName: 'gun', + depType: 'prod', packageName: 'grpc_gun', lockedVersion: '2.0.1', }, @@ -211,9 +253,27 @@ describe('modules/manager/mix/extract', () => { currentValue: '~> 0.4.0', datasource: 'hex', depName: 'another_gun', + depType: 'prod', packageName: 'raygun', lockedVersion: '0.4.0', }, + { + currentValue: '~> 1.7', + datasource: 'hex', + depName: 'credo', + depType: 'dev', + packageName: 'credo', + lockedVersion: '1.7.10', + }, + { + currentValue: '== 0.37.0', + currentVersion: '0.37.0', + datasource: 'hex', + depName: 'floki', + depType: 'dev', + lockedVersion: '0.37.0', + packageName: 'floki', + }, ]); }); }); diff --git a/lib/modules/manager/mix/extract.ts b/lib/modules/manager/mix/extract.ts index 3d9c45e00d74f4..9d8c5a90b65392 100644 --- a/lib/modules/manager/mix/extract.ts +++ b/lib/modules/manager/mix/extract.ts @@ -20,6 +20,8 @@ const lockedVersionRegExp = regEx( /^\s+"(?\w+)".*?"(?\d+\.\d+\.\d+)"/, ); const hexRegexp = regEx(/hex:\s*(?:"(?[^"]+)"|:(?\w+))/); +const onlyValueRegexp = regEx(/only:\s*(?\[[^\]]*\]|:\w+)/); +const onlyEnvironmentsRegexp = regEx(/:(\w+)/gm); export async function extractPackageFile( content: string, @@ -48,22 +50,28 @@ export async function extractPackageFile( const hexGroups = hexRegexp.exec(opts)?.groups; const hex = hexGroups?.strValue ?? hexGroups?.atomValue; - let dep: PackageDependency; + const onlyValue = onlyValueRegexp.exec(opts)?.groups?.only; + const onlyEnvironments = []; + let match; + if (onlyValue) { + while ((match = onlyEnvironmentsRegexp.exec(onlyValue)) !== null) { + onlyEnvironments.push(match[1]); + } + } + + const dep: PackageDependency = { + depName: app, + depType: 'prod', + }; if (git ?? github) { - dep = { - depName: app, - currentDigest: ref, - currentValue: branchOrTag, - datasource: git ? GitTagsDatasource.id : GithubTagsDatasource.id, - packageName: git ?? github, - }; + dep.currentDigest = ref; + dep.currentValue = branchOrTag; + dep.datasource = git ? GitTagsDatasource.id : GithubTagsDatasource.id; + dep.packageName = git ?? github; } else { - dep = { - depName: app, - currentValue: requirement, - datasource: HexDatasource.id, - }; + dep.currentValue = requirement; + dep.datasource = HexDatasource.id; if (organization) { dep.packageName = `${app}:${organization}`; } else if (hex) { @@ -71,11 +79,16 @@ export async function extractPackageFile( } else { dep.packageName = app; } + if (requirement?.startsWith('==')) { dep.currentVersion = requirement.replace(regEx(/^==\s*/), ''); } } + if (onlyValue !== undefined && !onlyEnvironments.includes('prod')) { + dep.depType = 'dev'; + } + deps.set(app, dep); logger.trace({ dep }, `setting ${app}`); depMatchGroups = depMatchRegExp.exec(depBuffer)?.groups; diff --git a/lib/modules/manager/mix/index.ts b/lib/modules/manager/mix/index.ts index e9722265580591..df1fa52ec6cf6d 100644 --- a/lib/modules/manager/mix/index.ts +++ b/lib/modules/manager/mix/index.ts @@ -5,6 +5,7 @@ import { HexDatasource } from '../../datasource/hex'; export { extractPackageFile } from './extract'; export { updateArtifacts } from './artifacts'; +export { getRangeStrategy } from './range'; export const url = 'https://hexdocs.pm/mix/Mix.html'; export const categories: Category[] = ['elixir']; diff --git a/lib/modules/manager/mix/range.spec.ts b/lib/modules/manager/mix/range.spec.ts new file mode 100644 index 00000000000000..0efd919e8455cb --- /dev/null +++ b/lib/modules/manager/mix/range.spec.ts @@ -0,0 +1,47 @@ +import type { RangeConfig } from '../types'; +import { getRangeStrategy } from '.'; + +describe('modules/manager/mix/range', () => { + it('returns same if not auto', () => { + const config: RangeConfig = { rangeStrategy: 'pin' }; + expect(getRangeStrategy(config)).toBe('pin'); + + config.rangeStrategy = 'widen'; + expect(getRangeStrategy(config)).toBe('widen'); + }); + + it('widens complex bump', () => { + const config: RangeConfig = { + rangeStrategy: 'bump', + depType: 'prod', + currentValue: '>= 1.6.0 and < 2.0.0', + }; + expect(getRangeStrategy(config)).toBe('widen'); + }); + + it('bumps non-complex bump', () => { + const config: RangeConfig = { + rangeStrategy: 'bump', + depType: 'prod', + currentValue: '~>1.0.0', + }; + expect(getRangeStrategy(config)).toBe('bump'); + }); + + it('widens complex auto', () => { + const config: RangeConfig = { + rangeStrategy: 'auto', + depType: 'prod', + currentValue: '<1.7.0 or ~>1.7.1', + }; + expect(getRangeStrategy(config)).toBe('widen'); + }); + + it('defaults to update-lockfile', () => { + const config: RangeConfig = { + rangeStrategy: 'auto', + depType: 'prod', + }; + expect(getRangeStrategy(config)).toBe('update-lockfile'); + }); +}); diff --git a/lib/modules/manager/mix/range.ts b/lib/modules/manager/mix/range.ts new file mode 100644 index 00000000000000..4d6e563eb5123e --- /dev/null +++ b/lib/modules/manager/mix/range.ts @@ -0,0 +1,26 @@ +import { parseRange } from 'semver-utils'; +import { logger } from '../../../logger'; +import type { RangeStrategy } from '../../../types'; +import type { RangeConfig } from '../types'; + +export function getRangeStrategy(config: RangeConfig): RangeStrategy { + const { currentValue, rangeStrategy } = config; + const isComplexRange = currentValue + ? parseRange(currentValue).length > 1 + : false; + + if (rangeStrategy === 'bump' && isComplexRange) { + logger.debug( + { currentValue }, + 'Replacing bump strategy for complex range with widen', + ); + return 'widen'; + } + if (rangeStrategy !== 'auto') { + return rangeStrategy; + } + if (isComplexRange) { + return 'widen'; + } + return 'update-lockfile'; +} diff --git a/lib/modules/manager/mix/readme.md b/lib/modules/manager/mix/readme.md index f63ab44019224e..7924c9704caa2e 100644 --- a/lib/modules/manager/mix/readme.md +++ b/lib/modules/manager/mix/readme.md @@ -1,3 +1,27 @@ -The `mix` manager extracts dependencies for the `hex` datasource and uses Renovate's implementation of Hex SemVer to evaluate updates. +The `mix` manager uses Renovate's implementation of [Elixir SemVer](https://hexdocs.pm/elixir/Version.html#module-requirements) to evaluate update ranges. -The `mix` package manager itself is also used to keep the lock file up-to-date. +The `mix` package manager itself is used to keep the lock file up-to-date. + +The following `depTypes` are currently supported by the `mix` manager : + +- `prod`: all dependencies by default +- `dev`: dependencies with [`:only` option](https://hexdocs.pm/mix/Mix.Tasks.Deps.html#module-dependency-definition-options) not containing `:prod` + +### Default `rangeStrategy=auto` behavior + +Renovate's default [`rangeStrategy`](../../../configuration-options.md#rangestrategy) is `"auto"`. +Here's how `"auto"` works with the `mix` manager: + +| Version type | New version | Old range | New range after update | What Renovate does | +| :----------------------- | :---------- | :-------------------- | :--------------------- | :------------------------------------------------------------------------ | +| Complex range | `1.7.2` | `< 1.7.0 or ~> 1.7.1` | `< 1.7.0 or ~> 1.7.2` | Widen range to include the new version. | +| Simple range | `0.39.0` | `<= 0.38.0` | `<= 0.39.0` | If update outside current range: widens range to include the new version. | +| Exact version constraint | `0.13.0` | `== 0.12.0` | `== 0.13.0` | Replace old version with new version. | + +### Recommended `rangeStrategy` for apps and libraries + +For applications, we recommend using `rangeStrategy=pin`. +This pins your dependencies to exact versions, which is generally considered [best practice for apps](../../../dependency-pinning.md). + +For libraries, use `rangeStrategy=widen` with version ranges in your `mix.exs`. +This allows for greater compatibility with other projects that may use your library as a dependency. diff --git a/lib/modules/manager/nix/extract.spec.ts b/lib/modules/manager/nix/extract.spec.ts index 9645744c2a9208..035a495eb0670f 100644 --- a/lib/modules/manager/nix/extract.spec.ts +++ b/lib/modules/manager/nix/extract.spec.ts @@ -1,74 +1,603 @@ +import { fs } from '../../../../test/util'; import { GitRefsDatasource } from '../../datasource/git-refs'; import { id as nixpkgsVersioning } from '../../versioning/nixpkgs'; import { extractPackageFile } from '.'; +jest.mock('../../../util/fs'); + describe('modules/manager/nix/extract', () => { - it('returns null when no nixpkgs', () => { - const content = `{ - inputs = {}; -}`; - const res = extractPackageFile(content); + const flake1Lock = `{ + "nodes": { + "root": {} + }, + "root": "root", + "version": 7 + }`; + const flake1Nix = `{ + inputs = {}; + }`; - expect(res).toBeNull(); + it('returns null when no nixpkgs input exists', async () => { + fs.readLocalFile.mockResolvedValueOnce(flake1Lock); + expect(await extractPackageFile(flake1Nix, 'flake.nix')).toBeNull(); }); - it('returns nixpkgs', () => { - const content = `{ - inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-21.11"; - }; -}`; + const flake2Nix = `{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-21.11"; + }; + }`; + + it('match nixpkgs input', async () => { + fs.readLocalFile.mockResolvedValueOnce(flake1Lock); + expect(await extractPackageFile(flake2Nix, 'flake.nix')).toEqual({ + deps: [ + { + depName: 'nixpkgs', + currentValue: 'nixos-21.11', + datasource: GitRefsDatasource.id, + packageName: 'https://github.com/NixOS/nixpkgs', + versioning: nixpkgsVersioning, + }, + ], + }); + }); - const res = extractPackageFile(content); + const flake3Nix = `{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-21.11"; + }; + }`; - expect(res?.deps).toEqual([ - { - depName: 'nixpkgs', - currentValue: 'nixos-21.11', - datasource: GitRefsDatasource.id, - packageName: 'https://github.com/NixOS/nixpkgs', - versioning: nixpkgsVersioning, + it('match nixpkgs input case insensitive', async () => { + fs.readLocalFile.mockResolvedValueOnce(flake1Lock); + expect(await extractPackageFile(flake3Nix, 'flake.nix')).toEqual({ + deps: [ + { + depName: 'nixpkgs', + currentValue: 'nixos-21.11', + datasource: GitRefsDatasource.id, + packageName: 'https://github.com/NixOS/nixpkgs', + versioning: nixpkgsVersioning, + }, + ], + }); + }); + + const flake4Nix = `{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs"; + }; + }`; + + it('includes nixpkgs input with no explicit ref', async () => { + fs.readLocalFile.mockResolvedValueOnce(flake1Lock); + expect(await extractPackageFile(flake4Nix, 'flake.nix')).toEqual({ + deps: [ + { + currentValue: undefined, + datasource: 'git-refs', + depName: 'nixpkgs', + packageName: 'https://github.com/NixOS/nixpkgs', + versioning: 'nixpkgs', + }, + ], + }); + }); + + it('returns null when no inputs', async () => { + fs.readLocalFile.mockResolvedValueOnce(flake1Lock); + expect(await extractPackageFile('', 'flake.nix')).toBeNull(); + }); + + const flake2Lock = `{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1720031269, + "narHash": "sha256-rwz8NJZV+387rnWpTYcXaRNvzUSnnF9aHONoJIYmiUQ=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9f4128e00b0ae8ec65918efeba59db998750ead6", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } }, - ]); + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 + }`; + + it('returns nixpkgs input', async () => { + fs.readLocalFile.mockResolvedValueOnce(flake2Lock); + expect(await extractPackageFile('', 'flake.nix')).toEqual({ + deps: [ + { + depName: 'nixpkgs', + currentDigest: '9f4128e00b0ae8ec65918efeba59db998750ead6', + currentValue: 'nixos-unstable', + datasource: GitRefsDatasource.id, + packageName: 'https://github.com/NixOS/nixpkgs', + }, + ], + }); }); - it('is case insensitive', () => { - const content = `{ - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-21.11"; - }; -}`; + const flake3Lock = `{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1728650607, + "narHash": "sha256-0lOnVTzRXzpk5uxbHLm3Ti3tyPAvirAIQDfwEUd8arg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "612ee628421ba2c1abca4c99684862f76cb3b089", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 + }`; - const res = extractPackageFile(content); + it('includes nixpkgs with no explicit ref', async () => { + fs.readLocalFile.mockResolvedValueOnce(flake3Lock); + expect(await extractPackageFile('', 'flake.nix')).toMatchObject({ + deps: [ + { + currentDigest: '612ee628421ba2c1abca4c99684862f76cb3b089', + datasource: 'git-refs', + depName: 'nixpkgs', + packageName: 'https://github.com/NixOS/nixpkgs', + }, + ], + }); + }); - expect(res?.deps).toEqual([ - { - depName: 'nixpkgs', - currentValue: 'nixos-21.11', - datasource: GitRefsDatasource.id, - packageName: 'https://github.com/NixOS/nixpkgs', - versioning: nixpkgsVersioning, + const flake4Lock = `{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1672057183, + "narHash": "sha256-GN7/10DNNvs1FPj9tlZA2qgNdFuYKKuS3qlHTqAxasQ=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b139e44d78c36c69bcbb825b20dbfa51e7738347", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixpkgs-unstable", + "type": "indirect" + } }, - ]); + "patchelf": { + "inputs": { + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1718457448, + "narHash": "sha256-FSoxTcRZMGHNJh8dNtKOkcUtjhmhU6yQXcZZfUPLhQM=", + "ref": "refs/heads/master", + "rev": "a0f54334df36770b335c051e540ba40afcbf8378", + "revCount": 844, + "type": "git", + "url": "https://github.com/NixOS/patchelf.git" + }, + "original": { + "type": "git", + "url": "https://github.com/NixOS/patchelf.git" + } + }, + "root": { + "inputs": { + "patchelf": "patchelf" + } + } + }, + "root": "root", + "version": 7 + }`; + + it('includes patchelf from HEAD', async () => { + fs.readLocalFile.mockResolvedValueOnce(flake4Lock); + expect(await extractPackageFile('', 'flake.nix')).toMatchObject({ + deps: [ + { + currentDigest: 'a0f54334df36770b335c051e540ba40afcbf8378', + datasource: 'git-refs', + depName: 'patchelf', + packageName: 'https://github.com/NixOS/patchelf.git', + }, + ], + }); }); - it('includes nixpkgs with no explicit ref', () => { - const content = `{ - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs"; - }; -}`; + const flake5Lock = `{ + "nodes": { + "ijq": { + "flake": false, + "locked": { + "lastModified": 1723569650, + "narHash": "sha256-Ho/sAhEUeSug52JALgjrKVUPCBe8+PovbJj/lniKxp8=", + "owner": "~gpanders", + "repo": "ijq", + "rev": "88f0d9ae98942bf49cba302c42b2a0f6e05f9b58", + "type": "sourcehut" + }, + "original": { + "owner": "~gpanders", + "repo": "ijq", + "type": "sourcehut" + } + }, + "root": { + "inputs": { + "ijq": "ijq" + } + } + }, + "root": "root", + "version": 7 + }`; - const res = extractPackageFile(content); + it('includes ijq from sourcehut without a flake', async () => { + fs.readLocalFile.mockResolvedValueOnce(flake5Lock); + expect(await extractPackageFile('', 'flake.nix')).toMatchObject({ + deps: [ + { + currentDigest: '88f0d9ae98942bf49cba302c42b2a0f6e05f9b58', + datasource: 'git-refs', + depName: 'ijq', + packageName: 'https://git.sr.ht/~gpanders/ijq', + }, + ], + }); + }); - expect(res).toMatchObject({ + const flake6Lock = `{ + "nodes": { + "home-manager": { + "flake": false, + "locked": { + "lastModified": 1728650932, + "narHash": "sha256-mGKzqdsRyLnGNl6WjEr7+sghGgBtYHhJQ4mjpgRTCsU=", + "owner": "rycee", + "repo": "home-manager", + "rev": "65ae9c147349829d3df0222151f53f79821c5134", + "type": "gitlab" + }, + "original": { + "owner": "rycee", + "repo": "home-manager", + "type": "gitlab" + } + }, + "root": { + "inputs": { + "home-manager": "home-manager" + } + } + }, + "root": "root", + "version": 7 + }`; + + it('includes home-manager from gitlab', async () => { + fs.readLocalFile.mockResolvedValueOnce(flake6Lock); + expect(await extractPackageFile('', 'flake.nix')).toMatchObject({ deps: [ { - currentValue: undefined, + currentDigest: '65ae9c147349829d3df0222151f53f79821c5134', + datasource: 'git-refs', + depName: 'home-manager', + packageName: 'https://gitlab.com/rycee/home-manager', + }, + ], + }); + }); + + const flake7Lock = `{ + "nodes": { + "root": {} + }, + "root": "root", + "version": 6 + }`; + + it('test other version', async () => { + fs.readLocalFile.mockResolvedValueOnce(flake7Lock); + expect(await extractPackageFile('', 'flake.nix')).toBeNull(); + }); + + const flake8Lock = `{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1728492678, + "narHash": "sha256-9UTxR8eukdg+XZeHgxW5hQA9fIKHsKCdOIUycTryeVw=", + "ref": "nixos-unstable", + "rev": "5633bcff0c6162b9e4b5f1264264611e950c8ec7", + "shallow": true, + "type": "git", + "url": "https://github.com/NixOS/nixpkgs" + }, + "original": { + "ref": "nixos-unstable", + "shallow": true, + "type": "git", + "url": "https://github.com/NixOS/nixpkgs" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 + }`; + + it('includes nixpkgs with ref and shallow arguments', async () => { + fs.readLocalFile.mockResolvedValueOnce(flake8Lock); + expect(await extractPackageFile('', 'flake.nix')).toMatchObject({ + deps: [ + { + currentDigest: '5633bcff0c6162b9e4b5f1264264611e950c8ec7', datasource: 'git-refs', depName: 'nixpkgs', packageName: 'https://github.com/NixOS/nixpkgs', - versioning: 'nixpkgs', + }, + ], + }); + }); + + const flake9Lock = `{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1728538411, + "narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 + }`; + + it('includes nixpkgs but using indirect type that cannot be updated', async () => { + fs.readLocalFile.mockResolvedValueOnce(flake9Lock); + expect(await extractPackageFile('', 'flake.nix')).toBeNull(); + }); + + const flake10Lock = `{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1726560853, + "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1728492678, + "narHash": "sha256-9UTxR8eukdg+XZeHgxW5hQA9fIKHsKCdOIUycTryeVw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "5633bcff0c6162b9e4b5f1264264611e950c8ec7", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-extra-pkgs": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + }, + "locked": { + "host": "github.corp.example.com", + "lastModified": 1728666512, + "narHash": "sha256-p+l16Zzyl2DXG695yks6KQP7NkjsnEksu5GBvtL1QYg=", + "owner": "my-org", + "repo": "nixpkgs-extra-pkgs", + "rev": "6bf2706348447df6f8b86b1c3e54f87b0afda84f", + "type": "github" + }, + "original": { + "host": "github.corp.example.com", + "owner": "my-org", + "repo": "nixpkgs-extra-pkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs-extra-pkgs": "nixpkgs-extra-pkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 + }`; + + it('includes flake from GitHub Enterprise', async () => { + fs.readLocalFile.mockResolvedValueOnce(flake10Lock); + expect(await extractPackageFile('', 'flake.nix')).toMatchObject({ + deps: [ + { + currentDigest: '6bf2706348447df6f8b86b1c3e54f87b0afda84f', + datasource: 'git-refs', + depName: 'nixpkgs-extra-pkgs', + packageName: + 'https://github.corp.example.com/my-org/nixpkgs-extra-pkgs', + }, + ], + }); + }); + + const flake11Lock = `{ + "nodes": { + "data-mesher": { + "inputs": { + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs", + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "lastModified": 1727355895, + "narHash": "sha256-grZIaLgk5GgoDuTt49RTCLBh458H4YJdIAU4B3onXRw=", + "rev": "c7e39452affcc0f89e023091524e38b3aaf109e9", + "type": "tarball", + "url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/c7e39452affcc0f89e023091524e38b3aaf109e9.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://git.clan.lol/clan/data-mesher/archive/main.tar.gz" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "data-mesher", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1726153070, + "narHash": "sha256-HO4zgY0ekfwO5bX0QH/3kJ/h4KvUDFZg8YpkNwIbg1U=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "bcef6817a8b2aa20a5a6dbb19b43e63c5bf8619a", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1726871744, + "narHash": "sha256-V5LpfdHyQkUF7RfOaDPrZDP+oqz88lTJrMT1+stXNwo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "a1d92660c6b3b7c26fb883500a80ea9d33321be2", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "data-mesher": "data-mesher" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "data-mesher", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1726734507, + "narHash": "sha256-VUH5O5AcOSxb0uL/m34dDkxFKP6WLQ6y4I1B4+N3L2w=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "ee41a466c2255a3abe6bc50fc6be927cdee57a9f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 + }`; + + it('includes flake with tarball type', async () => { + fs.readLocalFile.mockResolvedValueOnce(flake11Lock); + expect(await extractPackageFile('', 'flake.nix')).toMatchObject({ + deps: [ + { + currentDigest: 'c7e39452affcc0f89e023091524e38b3aaf109e9', + datasource: 'git-refs', + depName: 'data-mesher', + packageName: 'https://git.clan.lol/clan/data-mesher', }, ], }); diff --git a/lib/modules/manager/nix/extract.ts b/lib/modules/manager/nix/extract.ts index 5170e23a07dfe0..cfe99ead002906 100644 --- a/lib/modules/manager/nix/extract.ts +++ b/lib/modules/manager/nix/extract.ts @@ -1,11 +1,28 @@ +import { logger } from '../../../logger'; +import { getSiblingFileName, readLocalFile } from '../../../util/fs'; import { regEx } from '../../../util/regex'; import { GitRefsDatasource } from '../../datasource/git-refs'; import { id as nixpkgsVersioning } from '../../versioning/nixpkgs'; import type { PackageDependency, PackageFileContent } from '../types'; +import { NixFlakeLock } from './schema'; const nixpkgsRegex = regEx(/"github:nixos\/nixpkgs(\/(?[a-z0-9-.]+))?"/i); -export function extractPackageFile(content: string): PackageFileContent | null { +// as documented upstream +// https://github.com/NixOS/nix/blob/master/doc/manual/source/protocols/tarball-fetcher.md#gitea-and-forgejo-support +const lockableHTTPTarballProtocol = regEx( + '^https://(?[^/]+)/(?[^/]+)/(?[^/]+)/archive/(?.+).tar.gz$', +); + +export async function extractPackageFile( + content: string, + packageFile: string, +): Promise { + const packageLockFile = getSiblingFileName(packageFile, 'flake.lock'); + const lockContents = await readLocalFile(packageLockFile, 'utf8'); + + logger.trace(`nix.extractPackageFile(${packageLockFile})`); + const deps: PackageDependency[] = []; const match = nixpkgsRegex.exec(content); @@ -20,6 +37,118 @@ export function extractPackageFile(content: string): PackageFileContent | null { }); } + const flakeLockParsed = NixFlakeLock.safeParse(lockContents); + if (!flakeLockParsed.success) { + logger.debug( + { packageLockFile, error: flakeLockParsed.error }, + `invalid flake.lock file`, + ); + return null; + } + + const flakeLock = flakeLockParsed.data; + const rootInputs = flakeLock.nodes['root'].inputs; + + if (!rootInputs) { + logger.debug( + { packageLockFile, error: flakeLockParsed.error }, + `flake.lock is missing "root" node`, + ); + + if (deps.length) { + return { deps }; + } + return null; + } + + for (const [depName, flakeInput] of Object.entries(flakeLock.nodes)) { + // the root input is a magic string for the entrypoint and only references other flake inputs + if (depName === 'root') { + continue; + } + + // skip all locked and transitivie nodes as they cannot be updated by regular means + if (!(depName in rootInputs)) { + continue; + } + + const flakeLocked = flakeInput.locked; + const flakeOriginal = flakeInput.original; + + // istanbul ignore if: if we are not in a root node then original and locked always exist which cannot be easily expressed in the type + if (flakeLocked === undefined || flakeOriginal === undefined) { + logger.debug( + { packageLockFile, flakeInput }, + `Found empty flake input, skipping`, + ); + continue; + } + + // indirect inputs cannot be reliable updated because they depend on the flake registry + if (flakeOriginal.type === 'indirect') { + continue; + } + + switch (flakeLocked.type) { + case 'github': + deps.push({ + depName, + currentValue: flakeOriginal.ref, + currentDigest: flakeLocked.rev, + datasource: GitRefsDatasource.id, + packageName: `https://${flakeOriginal.host ?? 'github.com'}/${flakeOriginal.owner}/${flakeOriginal.repo}`, + }); + break; + case 'gitlab': + deps.push({ + depName, + currentValue: flakeOriginal.ref, + currentDigest: flakeLocked.rev, + datasource: GitRefsDatasource.id, + packageName: `https://${flakeOriginal.host ?? 'gitlab.com'}/${flakeOriginal.owner}/${flakeOriginal.repo}`, + }); + break; + case 'git': + deps.push({ + depName, + currentValue: flakeOriginal.ref, + currentDigest: flakeLocked.rev, + datasource: GitRefsDatasource.id, + packageName: flakeOriginal.url, + }); + break; + case 'sourcehut': + deps.push({ + depName, + currentValue: flakeOriginal.ref, + currentDigest: flakeLocked.rev, + datasource: GitRefsDatasource.id, + packageName: `https://${flakeOriginal.host ?? 'git.sr.ht'}/${flakeOriginal.owner}/${flakeOriginal.repo}`, + }); + break; + case 'tarball': + deps.push({ + depName, + currentValue: flakeLocked.ref, + currentDigest: flakeLocked.rev, + datasource: GitRefsDatasource.id, + // type tarball always contains this link + packageName: flakeOriginal.url!.replace( + lockableHTTPTarballProtocol, + 'https://$/$/$', + ), + }); + break; + // istanbul ignore next: just a safeguard + default: + logger.debug( + { packageLockFile }, + `Unknown flake.lock type "${flakeLocked.type}", skipping`, + ); + break; + } + } + if (deps.length) { return { deps }; } diff --git a/lib/modules/manager/nix/index.ts b/lib/modules/manager/nix/index.ts index 35fa06494f17eb..abbe3e6ec3e84f 100644 --- a/lib/modules/manager/nix/index.ts +++ b/lib/modules/manager/nix/index.ts @@ -9,7 +9,7 @@ export const url = 'https://nix.dev'; export const defaultConfig = { fileMatch: ['(^|/)flake\\.nix$'], - commitMessageTopic: 'nixpkgs', + commitMessageTopic: 'nix', commitMessageExtra: 'to {{newValue}}', enabled: false, }; diff --git a/lib/modules/manager/nix/readme.md b/lib/modules/manager/nix/readme.md index 3a196a34445c0e..af3d80c5b0b05e 100644 --- a/lib/modules/manager/nix/readme.md +++ b/lib/modules/manager/nix/readme.md @@ -1,4 +1,9 @@ The [`nix`](https://github.com/NixOS/nix) manager supports: - [`lockFileMaintenance`](../../../configuration-options.md#lockfilemaintenance) updates for `flake.lock` -- [nixpkgs](https://github.com/NixOS/nixpkgs) updates +- input updates for `flake.lock` + +For specifying `packageRules` it is important to know how `depName` and `packageName` are defined for nix updates: + +- The `depName` field is equal to the nix flake input name, eg. `nix.inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";` would have the `depName` of `nixpkgs` +- The `packageName` field is equal to the fully-qualified root URL of the package source, eg. `https://github.com/NixOS/nixpkgs` for the above example. diff --git a/lib/modules/manager/nix/schema.ts b/lib/modules/manager/nix/schema.ts new file mode 100644 index 00000000000000..af8e0e33e95dbf --- /dev/null +++ b/lib/modules/manager/nix/schema.ts @@ -0,0 +1,41 @@ +import { z } from 'zod'; +import { Json } from '../../../util/schema-utils'; + +const InputType = z.enum([ + 'git', + 'github', + 'gitlab', + 'indirect', + 'sourcehut', + 'tarball', +]); + +const LockedInput = z.object({ + ref: z.string().optional(), + rev: z.string(), + type: InputType, +}); + +const OriginalInput = z.object({ + host: z.string().optional(), + owner: z.string().optional(), + repo: z.string().optional(), + ref: z.string().optional(), + type: InputType, + url: z.string().optional(), +}); + +const NixInput = z.object({ + inputs: z.record(z.string(), z.string().or(z.array(z.string()))).optional(), + locked: LockedInput.optional(), + original: OriginalInput.optional(), +}); + +export const NixFlakeLock = Json.pipe( + z.object({ + nodes: z.record(z.string(), NixInput), + version: z.literal(7), + }), +); + +export type NixFlakeLock = z.infer; diff --git a/lib/modules/manager/npm/extract/common/package-file.ts b/lib/modules/manager/npm/extract/common/package-file.ts index b81fbf137fbdec..0e59b57cf70e96 100644 --- a/lib/modules/manager/npm/extract/common/package-file.ts +++ b/lib/modules/manager/npm/extract/common/package-file.ts @@ -1,3 +1,4 @@ +import { parsePkgAndParentSelector } from '@pnpm/parse-overrides'; import is from '@sindresorhus/is'; import { CONFIG_VALIDATION } from '../../../../../constants/error-messages'; import { logger } from '../../../../../logger'; @@ -91,7 +92,8 @@ export function extractPackageJson( )) { if (is.string(overridesVal)) { // Newer flat syntax: `parent>parent>child` - const packageName = overridesKey.split('>').pop()!; + const packageName = + parsePkgAndParentSelector(overridesKey).targetPkg.name; dep = { depName: overridesKey, packageName, diff --git a/lib/modules/manager/npm/extract/index.spec.ts b/lib/modules/manager/npm/extract/index.spec.ts index 715692c11896e8..a73936ce273b9a 100644 --- a/lib/modules/manager/npm/extract/index.spec.ts +++ b/lib/modules/manager/npm/extract/index.spec.ts @@ -1027,6 +1027,87 @@ describe('modules/manager/npm/extract/index', () => { ], }); }); + + it('extracts dependencies from pnpm.overrides, with version ranges in flat syntax', async () => { + const content = codeBlock`{ + "pnpm": { + "overrides": { + "foo>bar": "2.0.0", + "foo@1.0.0": "2.0.0", + "foo@>1.0.0": "2.0.0", + "foo@>=1.0.0": "2.0.0", + "foo@1.0.0>bar": "2.0.0", + "foo@>1.0.0>bar": "2.0.0", + "foo@>=1.0.0 <2.0.0": ">=2.0.0" + } + } + }`; + const res = await npmExtract.extractPackageFile( + content, + 'package.json', + defaultExtractConfig, + ); + expect(res).toMatchObject({ + deps: [ + { + currentValue: '2.0.0', + datasource: 'npm', + depName: 'foo>bar', + depType: 'pnpm.overrides', + packageName: 'bar', + prettyDepType: 'overrides', + }, + { + currentValue: '2.0.0', + datasource: 'npm', + depName: 'foo@1.0.0', + depType: 'pnpm.overrides', + packageName: 'foo', + prettyDepType: 'overrides', + }, + { + currentValue: '2.0.0', + datasource: 'npm', + depName: 'foo@>1.0.0', + depType: 'pnpm.overrides', + packageName: 'foo', + prettyDepType: 'overrides', + }, + { + currentValue: '2.0.0', + datasource: 'npm', + depName: 'foo@>=1.0.0', + depType: 'pnpm.overrides', + packageName: 'foo', + prettyDepType: 'overrides', + }, + { + currentValue: '2.0.0', + datasource: 'npm', + depName: 'foo@1.0.0>bar', + depType: 'pnpm.overrides', + packageName: 'bar', + prettyDepType: 'overrides', + }, + { + currentValue: '2.0.0', + datasource: 'npm', + depName: 'foo@>1.0.0>bar', + depType: 'pnpm.overrides', + packageName: 'bar', + prettyDepType: 'overrides', + }, + { + currentValue: '>=2.0.0', + datasource: 'npm', + depName: 'foo@>=1.0.0 <2.0.0', + depType: 'pnpm.overrides', + packageName: 'foo', + prettyDepType: 'overrides', + }, + ], + }); + }); }); describe('.extractAllPackageFiles()', () => { @@ -1078,6 +1159,36 @@ describe('modules/manager/npm/extract/index', () => { }, ]); }); + + it('extracts pnpm workspace yaml files', async () => { + fs.readLocalFile.mockResolvedValueOnce(codeBlock` + packages: + - pkg-a + + catalog: + is-positive: 1.0.0 + `); + const res = await extractAllPackageFiles(defaultExtractConfig, [ + 'pnpm-workspace.yaml', + ]); + expect(res).toEqual([ + { + deps: [ + { + currentValue: '1.0.0', + datasource: 'npm', + depName: 'is-positive', + depType: 'pnpm.catalog.default', + prettyDepType: 'pnpm.catalog.default', + }, + ], + managerData: { + pnpmShrinkwrap: undefined, + }, + packageFile: 'pnpm-workspace.yaml', + }, + ]); + }); }); describe('.postExtract()', () => { diff --git a/lib/modules/manager/npm/extract/index.ts b/lib/modules/manager/npm/extract/index.ts index 0b9fbba90638e0..d96f38bac16643 100644 --- a/lib/modules/manager/npm/extract/index.ts +++ b/lib/modules/manager/npm/extract/index.ts @@ -17,6 +17,7 @@ import type { import type { NpmLockFiles, NpmManagerData } from '../types'; import { getExtractedConstraints } from './common/dependency'; import { extractPackageJson } from './common/package-file'; +import { extractPnpmWorkspaceFile, tryParsePnpmWorkspaceYaml } from './pnpm'; import { postExtract } from './post'; import type { NpmPackage } from './types'; import { isZeroInstall } from './yarn'; @@ -229,12 +230,33 @@ export async function extractAllPackageFiles( const content = await readLocalFile(packageFile, 'utf8'); // istanbul ignore else if (content) { - const deps = await extractPackageFile(content, packageFile, config); - if (deps) { - npmFiles.push({ - ...deps, + // pnpm workspace files are their own package file, defined via fileMatch. + // We duck-type the content here, to allow users to rename the file itself. + const parsedPnpmWorkspaceYaml = tryParsePnpmWorkspaceYaml(content); + if (parsedPnpmWorkspaceYaml.success) { + logger.trace( + { packageFile }, + `Extracting file as a pnpm workspace YAML file`, + ); + const deps = await extractPnpmWorkspaceFile( + parsedPnpmWorkspaceYaml.data, packageFile, - }); + ); + if (deps) { + npmFiles.push({ + ...deps, + packageFile, + }); + } + } else { + logger.trace({ packageFile }, `Extracting as a package.json file`); + const deps = await extractPackageFile(content, packageFile, config); + if (deps) { + npmFiles.push({ + ...deps, + packageFile, + }); + } } } else { logger.debug({ packageFile }, `No content found`); diff --git a/lib/modules/manager/npm/extract/pnpm.spec.ts b/lib/modules/manager/npm/extract/pnpm.spec.ts index 5a18700e4a1f6a..9677ac719decc5 100644 --- a/lib/modules/manager/npm/extract/pnpm.spec.ts +++ b/lib/modules/manager/npm/extract/pnpm.spec.ts @@ -1,17 +1,20 @@ +import { codeBlock } from 'common-tags'; import { Fixtures } from '../../../../../test/fixtures'; -import { getFixturePath, logger, partial } from '../../../../../test/util'; +import { fs, getFixturePath, logger, partial } from '../../../../../test/util'; import { GlobalConfig } from '../../../../config/global'; -import * as fs from '../../../../util/fs'; import * as yaml from '../../../../util/yaml'; import type { PackageFile } from '../../types'; import type { NpmManagerData } from '../types'; import { detectPnpmWorkspaces, extractPnpmFilters, + extractPnpmWorkspaceFile, findPnpmWorkspace, getPnpmLock, } from './pnpm'; +jest.mock('../../../../util/fs'); + describe('modules/manager/npm/extract/pnpm', () => { beforeAll(() => { GlobalConfig.set({ localDir: getFixturePath('pnpm-monorepo/', '..') }); @@ -23,9 +26,7 @@ describe('modules/manager/npm/extract/pnpm', () => { describe('.extractPnpmFilters()', () => { it('detects errors in pnpm-workspace.yml file structure', async () => { - jest - .spyOn(fs, 'readLocalFile') - .mockResolvedValueOnce('p!!!ckages:\n - "packages/*"'); + fs.readLocalFile.mockResolvedValueOnce('p!!!ckages:\n - "packages/*"'); const workSpaceFilePath = getFixturePath( 'pnpm-monorepo/pnpm-workspace.yml', @@ -60,7 +61,7 @@ describe('modules/manager/npm/extract/pnpm', () => { describe('.findPnpmWorkspace()', () => { it('detects missing pnpm-workspace.yaml', async () => { - jest.spyOn(fs, 'findLocalSiblingOrParent').mockResolvedValueOnce(null); + fs.findLocalSiblingOrParent.mockResolvedValueOnce(null); const packageFile = 'package.json'; const res = await findPnpmWorkspace(packageFile); @@ -72,10 +73,8 @@ describe('modules/manager/npm/extract/pnpm', () => { }); it('detects missing pnpm-lock.yaml when pnpm-workspace.yaml was already found', async () => { - jest - .spyOn(fs, 'findLocalSiblingOrParent') - .mockResolvedValueOnce('pnpm-workspace.yaml'); - jest.spyOn(fs, 'localPathExists').mockResolvedValueOnce(false); + fs.findLocalSiblingOrParent.mockResolvedValueOnce('pnpm-workspace.yaml'); + fs.localPathExists.mockResolvedValueOnce(false); const packageFile = 'package.json'; const res = await findPnpmWorkspace(packageFile); @@ -91,7 +90,23 @@ describe('modules/manager/npm/extract/pnpm', () => { }); describe('.detectPnpmWorkspaces()', () => { + beforeEach(() => { + const realFs = jest.requireActual('../../../../util/fs'); + + // The real implementations of these functions are used for this block; + // they do static path manipulation. + fs.findLocalSiblingOrParent.mockImplementation( + realFs.findLocalSiblingOrParent, + ); + fs.getSiblingFileName.mockImplementation(realFs.getSiblingFileName); + + // Falls through to reading from the fixture path defined in GlobalConfig + // at the top of this file + fs.readLocalFile.mockImplementation(realFs.readLocalFile); + }); + it('uses pnpm workspaces', async () => { + fs.localPathExists.mockResolvedValue(true); const packageFiles = partial>([ { packageFile: 'package.json', @@ -197,6 +212,7 @@ describe('modules/manager/npm/extract/pnpm', () => { }); it('filters none matching packages', async () => { + fs.localPathExists.mockResolvedValue(true); const packageFiles = [ { packageFile: 'package.json', @@ -242,14 +258,14 @@ describe('modules/manager/npm/extract/pnpm', () => { describe('.getPnpmLock()', () => { it('returns empty if failed to parse', async () => { - jest.spyOn(fs, 'readLocalFile').mockResolvedValueOnce(undefined as never); + fs.readLocalFile.mockResolvedValueOnce(undefined as never); const res = await getPnpmLock('package.json'); expect(res.lockedVersionsWithPath).toBeUndefined(); }); it('extracts version from monorepo', async () => { const plocktest1Lock = Fixtures.get('pnpm-monorepo/pnpm-lock.yaml', '..'); - jest.spyOn(fs, 'readLocalFile').mockResolvedValueOnce(plocktest1Lock); + fs.readLocalFile.mockResolvedValueOnce(plocktest1Lock); const res = await getPnpmLock('package.json'); expect(Object.keys(res.lockedVersionsWithPath!)).toHaveLength(11); }); @@ -259,15 +275,176 @@ describe('modules/manager/npm/extract/pnpm', () => { 'lockfile-parsing/pnpm-lock.yaml', '..', ); - jest.spyOn(fs, 'readLocalFile').mockResolvedValueOnce(plocktest1Lock); + fs.readLocalFile.mockResolvedValueOnce(plocktest1Lock); const res = await getPnpmLock('package.json'); expect(Object.keys(res.lockedVersionsWithPath!)).toHaveLength(1); }); + it('extracts version from catalogs', async () => { + const lockfileContent = codeBlock` + lockfileVersion: '9.0' + + settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + + catalogs: + default: + react: + specifier: ^18 + version: 18.3.1 + + importers: + + .: + dependencies: + react: + specifier: 'catalog:' + version: 18.3.1 + + packages: + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + snapshots: + + js-tokens@4.0.0: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + `; + fs.readLocalFile.mockResolvedValueOnce(lockfileContent); + const res = await getPnpmLock('package.json'); + expect(Object.keys(res.lockedVersionsWithCatalog!)).toHaveLength(1); + }); + it('returns empty if no deps', async () => { - jest.spyOn(fs, 'readLocalFile').mockResolvedValueOnce('{}'); + fs.readLocalFile.mockResolvedValueOnce('{}'); const res = await getPnpmLock('package.json'); expect(res.lockedVersionsWithPath).toBeUndefined(); }); }); + + describe('.extractPnpmWorkspaceFile()', () => { + it('handles empty catalog entries', async () => { + expect( + await extractPnpmWorkspaceFile( + { catalog: {}, catalogs: {} }, + 'pnpm-workspace.yaml', + ), + ).toMatchObject({ + deps: [], + }); + }); + + it('parses valid pnpm-workspace.yaml file', async () => { + expect( + await extractPnpmWorkspaceFile( + { + catalog: { + react: '18.3.0', + }, + catalogs: { + react17: { + react: '17.0.2', + }, + }, + }, + 'pnpm-workspace.yaml', + ), + ).toMatchObject({ + deps: [ + { + currentValue: '18.3.0', + datasource: 'npm', + depName: 'react', + depType: 'pnpm.catalog.default', + prettyDepType: 'pnpm.catalog.default', + }, + { + currentValue: '17.0.2', + datasource: 'npm', + depName: 'react', + depType: 'pnpm.catalog.react17', + prettyDepType: 'pnpm.catalog.react17', + }, + ], + }); + }); + + it('finds relevant lockfile', async () => { + const lockfileContent = codeBlock` + lockfileVersion: '9.0' + + catalogs: + default: + react: + specifier: 18.3.1 + version: 18.3.1 + + importers: + + .: + dependencies: + react: + specifier: 'catalog:' + version: 18.3.1 + + packages: + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + snapshots: + + js-tokens@4.0.0: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + `; + fs.readLocalFile.mockResolvedValueOnce(lockfileContent); + fs.getSiblingFileName.mockReturnValueOnce('pnpm-lock.yaml'); + expect( + await extractPnpmWorkspaceFile( + { + catalog: { + react: '18.3.1', + }, + }, + 'pnpm-workspace.yaml', + ), + ).toMatchObject({ + managerData: { + pnpmShrinkwrap: 'pnpm-lock.yaml', + }, + }); + }); + }); }); diff --git a/lib/modules/manager/npm/extract/pnpm.ts b/lib/modules/manager/npm/extract/pnpm.ts index 4871ed0fc5baa4..3bd596e1279e6c 100644 --- a/lib/modules/manager/npm/extract/pnpm.ts +++ b/lib/modules/manager/npm/extract/pnpm.ts @@ -1,6 +1,7 @@ import is from '@sindresorhus/is'; import { findPackages } from 'find-packages'; import upath from 'upath'; +import type { z } from 'zod'; import { GlobalConfig } from '../../../../config/global'; import { logger } from '../../../../logger'; import { @@ -10,10 +11,17 @@ import { readLocalFile, } from '../../../../util/fs'; import { parseSingleYaml } from '../../../../util/yaml'; -import type { PackageFile } from '../../types'; +import type { + PackageDependency, + PackageFile, + PackageFileContent, +} from '../../types'; import type { PnpmDependencySchema, PnpmLockFile } from '../post-update/types'; +import type { PnpmCatalogsSchema } from '../schema'; +import { PnpmWorkspaceFileSchema } from '../schema'; import type { NpmManagerData } from '../types'; -import type { LockFile, PnpmWorkspaceFile } from './types'; +import { extractDependency, parseDepName } from './common/dependency'; +import type { LockFile, PnpmCatalog, PnpmWorkspaceFile } from './types'; function isPnpmLockfile(obj: any): obj is PnpmLockFile { return is.plainObject(obj) && 'lockfileVersion' in obj; @@ -87,7 +95,7 @@ export async function detectPnpmWorkspaces( for (const p of packageFiles) { const { packageFile, managerData } = p; - const { pnpmShrinkwrap } = managerData as NpmManagerData; + const pnpmShrinkwrap = managerData?.pnpmShrinkwrap; // check if pnpmShrinkwrap-file has already been provided if (pnpmShrinkwrap) { @@ -160,9 +168,11 @@ export async function getPnpmLock(filePath: string): Promise { : parseFloat(lockParsed.lockfileVersion); const lockedVersions = getLockedVersions(lockParsed); + const lockedCatalogVersions = getLockedCatalogVersions(lockParsed); return { lockedVersionsWithPath: lockedVersions, + lockedVersionsWithCatalog: lockedCatalogVersions, lockfileVersion, }; } catch (err) { @@ -171,6 +181,26 @@ export async function getPnpmLock(filePath: string): Promise { } } +function getLockedCatalogVersions( + lockParsed: PnpmLockFile, +): Record> { + const lockedVersions: Record> = {}; + + if (is.nonEmptyObject(lockParsed.catalogs)) { + for (const [catalog, dependencies] of Object.entries(lockParsed.catalogs)) { + const versions: Record = {}; + + for (const [dep, versionCarrier] of Object.entries(dependencies)) { + versions[dep] = versionCarrier.version; + } + + lockedVersions[catalog] = versions; + } + } + + return lockedVersions; +} + function getLockedVersions( lockParsed: PnpmLockFile, ): Record>> { @@ -222,3 +252,101 @@ function getLockedDependencyVersions( return res; } + +export function tryParsePnpmWorkspaceYaml(content: string): + | { + success: true; + data: PnpmWorkspaceFile; + } + | { success: false; data?: never } { + try { + const data = parseSingleYaml(content, { + customSchema: PnpmWorkspaceFileSchema, + }); + return { success: true, data }; + } catch { + return { success: false }; + } +} + +type PnpmCatalogs = z.TypeOf; + +export async function extractPnpmWorkspaceFile( + catalogs: PnpmCatalogs, + packageFile: string, +): Promise | null> { + logger.trace(`pnpm.extractPnpmWorkspaceFile(${packageFile})`); + + const pnpmCatalogs = pnpmCatalogsToArray(catalogs); + + const deps = extractPnpmCatalogDeps(pnpmCatalogs); + + let pnpmShrinkwrap; + const filePath = getSiblingFileName(packageFile, 'pnpm-lock.yaml'); + + if (await readLocalFile(filePath, 'utf8')) { + pnpmShrinkwrap = filePath; + } + + return { + deps, + managerData: { + pnpmShrinkwrap, + }, + }; +} + +/** + * In order to facilitate matching on specific catalogs, we structure the + * depType as `pnpm.catalog.default`, `pnpm.catalog.react17`, and so on. + */ +function getCatalogDepType(name: string): string { + const CATALOG_DEPENDENCY = 'pnpm.catalog'; + return `${CATALOG_DEPENDENCY}.${name}`; +} + +function extractPnpmCatalogDeps( + catalogs: PnpmCatalog[], +): PackageDependency[] { + const deps: PackageDependency[] = []; + + for (const catalog of catalogs) { + for (const [key, val] of Object.entries(catalog.dependencies)) { + const depType = getCatalogDepType(catalog.name); + const depName = parseDepName(depType, key); + const dep: PackageDependency = { + depType, + depName, + ...extractDependency(depType, depName, val!), + prettyDepType: depType, + }; + deps.push(dep); + } + } + + return deps; +} + +function pnpmCatalogsToArray({ + catalog: defaultCatalogDeps, + catalogs: namedCatalogs, +}: PnpmCatalogs): PnpmCatalog[] { + const result: PnpmCatalog[] = []; + + if (defaultCatalogDeps !== undefined) { + result.push({ name: 'default', dependencies: defaultCatalogDeps }); + } + + if (!namedCatalogs) { + return result; + } + + for (const [name, dependencies] of Object.entries(namedCatalogs)) { + result.push({ + name, + dependencies, + }); + } + + return result; +} diff --git a/lib/modules/manager/npm/extract/post/locked-versions.spec.ts b/lib/modules/manager/npm/extract/post/locked-versions.spec.ts index e141766d63f973..fffffa6d567eda 100644 --- a/lib/modules/manager/npm/extract/post/locked-versions.spec.ts +++ b/lib/modules/manager/npm/extract/post/locked-versions.spec.ts @@ -586,6 +586,66 @@ describe('modules/manager/npm/extract/post/locked-versions', () => { ]); }); + it('uses pnpm-lock for pnpm.catalog depType', async () => { + pnpm.getPnpmLock.mockResolvedValue({ + lockedVersionsWithCatalog: { + default: { + a: '1.0.0', + }, + named: { + b: '2.0.0', + }, + }, + lockfileVersion: 9.0, + }); + const packageFiles = [ + { + managerData: { + pnpmShrinkwrap: 'pnpm-lock.yaml', + }, + extractedConstraints: { + pnpm: '9.15.3', + }, + deps: [ + { + depName: 'a', + depType: 'pnpm.catalog.default', + currentValue: '1.0.0', + }, + { + depName: 'b', + depType: 'pnpm.catalog.named', + currentValue: '2.0.0', + }, + ], + packageFile: 'pnpm-workspace.yaml', + }, + ]; + await getLockedVersions(packageFiles); + expect(packageFiles).toEqual([ + { + extractedConstraints: { pnpm: '9.15.3' }, + deps: [ + { + currentValue: '1.0.0', + depName: 'a', + lockedVersion: '1.0.0', + depType: 'pnpm.catalog.default', + }, + { + currentValue: '2.0.0', + depName: 'b', + lockedVersion: '2.0.0', + depType: 'pnpm.catalog.named', + }, + ], + lockFiles: ['pnpm-lock.yaml'], + managerData: { pnpmShrinkwrap: 'pnpm-lock.yaml' }, + packageFile: 'pnpm-workspace.yaml', + }, + ]); + }); + it('uses pnpm-lock in subfolder', async () => { pnpm.getPnpmLock.mockResolvedValue({ lockedVersionsWithPath: { diff --git a/lib/modules/manager/npm/extract/post/locked-versions.ts b/lib/modules/manager/npm/extract/post/locked-versions.ts index 0970c3faeca509..c86a4e67424542 100644 --- a/lib/modules/manager/npm/extract/post/locked-versions.ts +++ b/lib/modules/manager/npm/extract/post/locked-versions.ts @@ -8,6 +8,9 @@ import { getNpmLock } from '../npm'; import { getPnpmLock } from '../pnpm'; import type { LockFile } from '../types'; import { getYarnLock, getYarnVersionFromLock } from '../yarn'; + +const pnpmCatalogDepTypeRe = /pnpm\.catalog\.(?.*)/; + export async function getLockedVersions( packageFiles: PackageFile[], ): Promise { @@ -121,14 +124,31 @@ export async function getLockedVersions( for (const dep of packageFile.deps) { const { depName, depType } = dep; - // TODO: types (#22198) - const lockedVersion = semver.valid( - lockFileCache[pnpmShrinkwrap].lockedVersionsWithPath?.[relativeDir]?.[ - depType! - ]?.[depName!], - ); - if (is.string(lockedVersion)) { - dep.lockedVersion = lockedVersion; + + const catalogName = pnpmCatalogDepTypeRe.exec(depType!)?.groups + ?.version; + + if (catalogName) { + const lockedVersion = semver.valid( + lockFileCache[pnpmShrinkwrap].lockedVersionsWithCatalog?.[ + catalogName + ]?.[depName!], + ); + + if (is.string(lockedVersion)) { + dep.lockedVersion = lockedVersion; + } + } else { + // TODO: types (#22198) + const lockedVersion = semver.valid( + lockFileCache[pnpmShrinkwrap].lockedVersionsWithPath?.[ + relativeDir + ]?.[depType!]?.[depName!], + ); + + if (is.string(lockedVersion)) { + dep.lockedVersion = lockedVersion; + } } } } diff --git a/lib/modules/manager/npm/extract/types.ts b/lib/modules/manager/npm/extract/types.ts index a9681aec758eb1..46f7fb220fc12a 100644 --- a/lib/modules/manager/npm/extract/types.ts +++ b/lib/modules/manager/npm/extract/types.ts @@ -30,12 +30,24 @@ export interface LockFile { string, Record> >; + lockedVersionsWithCatalog?: Record>; lockfileVersion?: number; // cache version for Yarn isYarn1?: boolean; } export interface PnpmWorkspaceFile { packages: string[]; + catalog?: Record; + catalogs?: Record>; +} + +/** + * A pnpm catalog is either the default catalog (catalog:, catalogs:default), or + * a named one (catalogs:) + */ +export interface PnpmCatalog { + name: string; + dependencies: NpmPackageDependency; } export type OverrideDependency = Record; diff --git a/lib/modules/manager/npm/index.ts b/lib/modules/manager/npm/index.ts index 9385b7606c20f6..1c120457c43243 100644 --- a/lib/modules/manager/npm/index.ts +++ b/lib/modules/manager/npm/index.ts @@ -20,7 +20,7 @@ export const url = 'https://docs.npmjs.com'; export const categories: Category[] = ['js']; export const defaultConfig = { - fileMatch: ['(^|/)package\\.json$'], + fileMatch: ['(^|/)package\\.json$', '(^|/)pnpm-workspace\\.yaml$'], digest: { prBodyDefinitions: { Change: diff --git a/lib/modules/manager/npm/post-update/index.ts b/lib/modules/manager/npm/post-update/index.ts index b77df6d1b0d86f..6aafc2ef08f474 100644 --- a/lib/modules/manager/npm/post-update/index.ts +++ b/lib/modules/manager/npm/post-update/index.ts @@ -242,7 +242,12 @@ export async function writeUpdatedPackageFiles( await writeLocalFile(packageFile.path, packageFile.contents!); continue; } - if (!packageFile.path.endsWith('package.json')) { + if ( + !( + packageFile.path.endsWith('package.json') || + packageFile.path.endsWith('pnpm-workspace.yaml') + ) + ) { continue; } logger.debug(`Writing ${packageFile.path}`); @@ -370,14 +375,6 @@ export async function getAdditionalFiles( logger.debug('Skipping lock file generation'); return { artifactErrors, updatedArtifacts }; } - if ( - config.reuseExistingBranch && - !config.updatedPackageFiles?.length && - config.upgrades?.every((upgrade) => upgrade.isLockfileUpdate) - ) { - logger.debug('Existing branch contains all necessary lock file updates'); - return { artifactErrors, updatedArtifacts }; - } logger.debug('Getting updated lock files'); if ( config.updateType === 'lockFileMaintenance' && diff --git a/lib/modules/manager/npm/post-update/pnpm.spec.ts b/lib/modules/manager/npm/post-update/pnpm.spec.ts index 6c9d28db1b8a50..41c7e9dffac96c 100644 --- a/lib/modules/manager/npm/post-update/pnpm.spec.ts +++ b/lib/modules/manager/npm/post-update/pnpm.spec.ts @@ -104,13 +104,13 @@ describe('modules/manager/npm/post-update/pnpm', () => { fs.readLocalFile.mockResolvedValue('package-lock-contents'); const res = await pnpmHelper.generateLockFile('some-folder', {}, config, [ { - groupName: 'some-group', + sharedVariableName: 'some-group', packageName: 'some-dep', newVersion: '1.1.0', isLockfileUpdate: true, }, { - groupName: 'some-group', + sharedVariableName: 'some-group', packageName: 'some-other-dep', newVersion: '1.1.0', isLockfileUpdate: false, diff --git a/lib/modules/manager/npm/post-update/types.ts b/lib/modules/manager/npm/post-update/types.ts index 35efbdd2f790e4..2ffe4d7f41bf0f 100644 --- a/lib/modules/manager/npm/post-update/types.ts +++ b/lib/modules/manager/npm/post-update/types.ts @@ -35,6 +35,7 @@ export type PnpmDependencySchema = Record; export interface PnpmLockFile { lockfileVersion: number | string; + catalogs?: Record>; importers?: Record>; dependencies: PnpmDependencySchema; devDependencies: PnpmDependencySchema; diff --git a/lib/modules/manager/npm/readme.md b/lib/modules/manager/npm/readme.md index a5d8549e57ab5f..09c5110d315474 100644 --- a/lib/modules/manager/npm/readme.md +++ b/lib/modules/manager/npm/readme.md @@ -7,6 +7,10 @@ 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` +- `overrides` +- `resolutions` +- `pnpm.overrides` +- `pnpm.catalog.`, such as `pnpm.catalog.default` and `pnpm.catalog.myCatalog`. [Matches any default and named pnpm catalogs](https://pnpm.io/catalogs#defining-catalogs). ### Yarn diff --git a/lib/modules/manager/npm/schema.ts b/lib/modules/manager/npm/schema.ts index 79d986fea78507..7efbf9a0f919fb 100644 --- a/lib/modules/manager/npm/schema.ts +++ b/lib/modules/manager/npm/schema.ts @@ -1,6 +1,17 @@ import { z } from 'zod'; import { Json, LooseRecord } from '../../../util/schema-utils'; +export const PnpmCatalogsSchema = z.object({ + catalog: z.optional(z.record(z.string())), + catalogs: z.optional(z.record(z.record(z.string()))), +}); + +export const PnpmWorkspaceFileSchema = z + .object({ + packages: z.array(z.string()), + }) + .and(PnpmCatalogsSchema); + export const PackageManagerSchema = z .string() .transform((val) => val.split('@')) diff --git a/lib/modules/manager/npm/update/dependency/common.ts b/lib/modules/manager/npm/update/dependency/common.ts new file mode 100644 index 00000000000000..7a84d4e21f5783 --- /dev/null +++ b/lib/modules/manager/npm/update/dependency/common.ts @@ -0,0 +1,32 @@ +import { logger } from '../../../../../logger'; +import type { Upgrade } from '../../../types'; + +export function getNewGitValue(upgrade: Upgrade): string | null { + if (!upgrade.currentRawValue) { + return null; + } + if (upgrade.currentDigest) { + logger.debug('Updating git digest'); + return upgrade.currentRawValue.replace( + upgrade.currentDigest, + // TODO #22198 + upgrade.newDigest!.substring(0, upgrade.currentDigest.length), + ); + } else { + logger.debug('Updating git version tag'); + return upgrade.currentRawValue.replace( + upgrade.currentValue, + upgrade.newValue, + ); + } +} + +export function getNewNpmAliasValue( + value: string | undefined, + upgrade: Upgrade, +): string | null { + if (!upgrade.npmPackageAlias) { + return null; + } + return `npm:${upgrade.packageName}@${value}`; +} diff --git a/lib/modules/manager/npm/update/dependency/index.ts b/lib/modules/manager/npm/update/dependency/index.ts index b6e1ca0735a0b3..0d4bb218f0205e 100644 --- a/lib/modules/manager/npm/update/dependency/index.ts +++ b/lib/modules/manager/npm/update/dependency/index.ts @@ -11,6 +11,8 @@ import type { RecursiveOverride, } from '../../extract/types'; import type { NpmDepType, NpmManagerData } from '../../types'; +import { getNewGitValue, getNewNpmAliasValue } from './common'; +import { updatePnpmCatalogDependency } from './pnpm'; function renameObjKey( oldObj: DependenciesMeta, @@ -115,29 +117,16 @@ export function updateDependency({ fileContent, upgrade, }: UpdateDependencyConfig): string | null { + if (upgrade.depType?.startsWith('pnpm.catalog')) { + return updatePnpmCatalogDependency({ fileContent, upgrade }); + } + const { depType, managerData } = upgrade; const depName: string = managerData?.key || upgrade.depName; let { newValue } = upgrade; - if (upgrade.currentRawValue) { - if (upgrade.currentDigest) { - logger.debug('Updating package.json git digest'); - newValue = upgrade.currentRawValue.replace( - upgrade.currentDigest, - // TODO #22198 - upgrade.newDigest!.substring(0, upgrade.currentDigest.length), - ); - } else { - logger.debug('Updating package.json git version tag'); - newValue = upgrade.currentRawValue.replace( - upgrade.currentValue, - upgrade.newValue, - ); - } - } - if (upgrade.npmPackageAlias) { - newValue = `npm:${upgrade.packageName}@${newValue}`; - } + newValue = getNewGitValue(upgrade) ?? newValue; + newValue = getNewNpmAliasValue(newValue, upgrade) ?? newValue; logger.debug(`npm.updateDependency(): ${depType}.${depName} = ${newValue}`); try { diff --git a/lib/modules/manager/npm/update/dependency/pnpm.spec.ts b/lib/modules/manager/npm/update/dependency/pnpm.spec.ts new file mode 100644 index 00000000000000..211371239b004d --- /dev/null +++ b/lib/modules/manager/npm/update/dependency/pnpm.spec.ts @@ -0,0 +1,574 @@ +import { codeBlock } from 'common-tags'; +import * as npmUpdater from '../..'; + +describe('modules/manager/npm/update/dependency/pnpm', () => { + it('handles implicit default catalog dependency', () => { + const upgrade = { + depType: 'pnpm.catalog.default', + depName: 'react', + newValue: '19.0.0', + }; + const pnpmWorkspaceYaml = codeBlock` + packages: + - pkg-a + + catalog: + react: 18.3.1 + `; + const testContent = npmUpdater.updateDependency({ + fileContent: pnpmWorkspaceYaml, + upgrade, + }); + expect(testContent).toEqual(codeBlock` + packages: + - pkg-a + + catalog: + react: 19.0.0 + `); + }); + + it('handles explicit default catalog dependency', () => { + const upgrade = { + depType: 'pnpm.catalog.default', + depName: 'react', + newValue: '19.0.0', + }; + const pnpmWorkspaceYaml = codeBlock` + packages: + - pkg-a + + catalogs: + default: + react: 18.3.1 + `; + const testContent = npmUpdater.updateDependency({ + fileContent: pnpmWorkspaceYaml, + upgrade, + }); + expect(testContent).toEqual(codeBlock` + packages: + - pkg-a + + catalogs: + default: + react: 19.0.0 + `); + }); + + it('handles explicit named catalog dependency', () => { + const upgrade = { + depType: 'pnpm.catalog.react17', + depName: 'react', + newValue: '19.0.0', + }; + const pnpmWorkspaceYaml = codeBlock` + packages: + - pkg-a + + catalog: + react: 18.3.1 + + catalogs: + react17: + react: 17.0.0 + `; + const testContent = npmUpdater.updateDependency({ + fileContent: pnpmWorkspaceYaml, + upgrade, + }); + expect(testContent).toEqual(codeBlock` + packages: + - pkg-a + + catalog: + react: 18.3.1 + + catalogs: + react17: + react: 19.0.0 + + `); + }); + + it('does nothing if the new and old values match', () => { + const upgrade = { + depType: 'pnpm.catalog.default', + depName: 'react', + newValue: '19.0.0', + }; + const pnpmWorkspaceYaml = codeBlock` + packages: + - pkg-a + + catalog: + react: 19.0.0 + `; + const testContent = npmUpdater.updateDependency({ + fileContent: pnpmWorkspaceYaml, + upgrade, + }); + expect(testContent).toEqual(pnpmWorkspaceYaml); + }); + + it('replaces package', () => { + const upgrade = { + depType: 'pnpm.catalog.default', + depName: 'config', + newName: 'abc', + newValue: '2.0.0', + }; + const pnpmWorkspaceYaml = codeBlock` + packages: + - pkg-a + + catalog: + config: 1.21.0 + `; + const testContent = npmUpdater.updateDependency({ + fileContent: pnpmWorkspaceYaml, + upgrade, + }); + expect(testContent).toEqual(codeBlock` + packages: + - pkg-a + + catalog: + abc: 2.0.0 + `); + }); + + it('replaces a github dependency value', () => { + const upgrade = { + depType: 'pnpm.catalog.default', + depName: 'gulp', + currentValue: 'v4.0.0-alpha.2', + currentRawValue: 'gulpjs/gulp#v4.0.0-alpha.2', + newValue: 'v4.0.0', + }; + const pnpmWorkspaceYaml = codeBlock` + packages: + - pkg-a + + catalog: + gulp: gulpjs/gulp#v4.0.0-alpha.2 + `; + const testContent = npmUpdater.updateDependency({ + fileContent: pnpmWorkspaceYaml, + upgrade, + }); + expect(testContent).toEqual(codeBlock` + packages: + - pkg-a + + catalog: + gulp: gulpjs/gulp#v4.0.0 + `); + }); + + it('replaces a npm package alias', () => { + const upgrade = { + depType: 'pnpm.catalog.default', + depName: 'hapi', + npmPackageAlias: true, + packageName: '@hapi/hapi', + currentValue: '18.3.0', + newValue: '18.3.1', + }; + const pnpmWorkspaceYaml = codeBlock` + packages: + - pkg-a + + catalog: + hapi: npm:@hapi/hapi@18.3.0 + `; + const testContent = npmUpdater.updateDependency({ + fileContent: pnpmWorkspaceYaml, + upgrade, + }); + expect(testContent).toEqual(codeBlock` + packages: + - pkg-a + + catalog: + hapi: npm:@hapi/hapi@18.3.1 + `); + }); + + it('replaces a github short hash', () => { + const upgrade = { + depType: 'pnpm.catalog.default', + depName: 'gulp', + currentDigest: 'abcdef7', + currentRawValue: 'gulpjs/gulp#abcdef7', + newDigest: '0000000000111111111122222222223333333333', + }; + const pnpmWorkspaceYaml = codeBlock` + packages: + - pkg-a + + catalog: + gulp: gulpjs/gulp#abcdef7 + `; + const testContent = npmUpdater.updateDependency({ + fileContent: pnpmWorkspaceYaml, + upgrade, + }); + expect(testContent).toEqual(codeBlock` + packages: + - pkg-a + + catalog: + gulp: gulpjs/gulp#0000000 + `); + }); + + it('replaces a github fully specified version', () => { + const upgrade = { + depType: 'pnpm.catalog.default', + depName: 'n', + currentValue: 'v1.0.0', + currentRawValue: 'git+https://github.com/owner/n#v1.0.0', + newValue: 'v1.1.0', + }; + const pnpmWorkspaceYaml = codeBlock` + packages: + - pkg-a + + catalog: + n: git+https://github.com/owner/n#v1.0.0 + `; + const testContent = npmUpdater.updateDependency({ + fileContent: pnpmWorkspaceYaml, + upgrade, + }); + expect(testContent).toEqual(codeBlock` + packages: + - pkg-a + + catalog: + n: git+https://github.com/owner/n#v1.1.0 + `); + }); + + it('returns null if the dependency is not present in the target catalog', () => { + const upgrade = { + depType: 'pnpm.catalog.default', + depName: 'react-not', + newValue: '19.0.0', + }; + const pnpmWorkspaceYaml = codeBlock` + packages: + - pkg-a + + catalog: + react: 18.3.1 + `; + const testContent = npmUpdater.updateDependency({ + fileContent: pnpmWorkspaceYaml, + upgrade, + }); + expect(testContent).toBeNull(); + }); + + it('returns null if catalogs are missing', () => { + const upgrade = { + depType: 'pnpm.catalog.default', + depName: 'react', + newValue: '19.0.0', + }; + const pnpmWorkspaceYaml = codeBlock` + packages: + - pkg-a + `; + const testContent = npmUpdater.updateDependency({ + fileContent: pnpmWorkspaceYaml, + upgrade, + }); + expect(testContent).toBeNull(); + }); + + it('returns null if empty file', () => { + const upgrade = { + depType: 'pnpm.catalog.default', + depName: 'react', + newValue: '19.0.0', + }; + const testContent = npmUpdater.updateDependency({ + fileContent: null as never, + upgrade, + }); + expect(testContent).toBeNull(); + }); + + it('preserves literal whitespace', () => { + const upgrade = { + depType: 'pnpm.catalog.default', + depName: 'react', + newValue: '19.0.0', + }; + const pnpmWorkspaceYaml = codeBlock` + packages: + - pkg-a + + catalog: + react: 18.3.1 + `; + const testContent = npmUpdater.updateDependency({ + fileContent: pnpmWorkspaceYaml, + upgrade, + }); + expect(testContent).toEqual(codeBlock` + packages: + - pkg-a + + catalog: + react: 19.0.0 + `); + }); + + it('preserves single quote style', () => { + const upgrade = { + depType: 'pnpm.catalog.default', + depName: 'react', + newValue: '19.0.0', + }; + const pnpmWorkspaceYaml = codeBlock` + packages: + - pkg-a + + catalog: + react: '18.3.1' + `; + const testContent = npmUpdater.updateDependency({ + fileContent: pnpmWorkspaceYaml, + upgrade, + }); + expect(testContent).toEqual(codeBlock` + packages: + - pkg-a + + catalog: + react: '19.0.0' + `); + }); + + it('preserves comments', () => { + const upgrade = { + depType: 'pnpm.catalog.default', + depName: 'react', + newValue: '19.0.0', + }; + const pnpmWorkspaceYaml = codeBlock` + packages: + - pkg-a + + catalog: + react: 18.3.1 # This is a comment + # This is another comment + react-dom: 18.3.1 + `; + const testContent = npmUpdater.updateDependency({ + fileContent: pnpmWorkspaceYaml, + upgrade, + }); + expect(testContent).toEqual(codeBlock` + packages: + - pkg-a + + catalog: + react: 19.0.0 # This is a comment + # This is another comment + react-dom: 18.3.1 + `); + }); + + it('preserves double quote style', () => { + const upgrade = { + depType: 'pnpm.catalog.default', + depName: 'react', + newValue: '19.0.0', + }; + const pnpmWorkspaceYaml = codeBlock` + packages: + - pkg-a + + catalog: + react: "18.3.1" + `; + const testContent = npmUpdater.updateDependency({ + fileContent: pnpmWorkspaceYaml, + upgrade, + }); + expect(testContent).toEqual(codeBlock` + packages: + - pkg-a + + catalog: + react: "19.0.0" + `); + }); + + it('preserves anchors, replacing only the value', () => { + // At the time of writing, this pattern is the recommended way to sync + // dependencies in catalogs. + // @see https://github.com/pnpm/pnpm/issues/8245#issuecomment-2371335323 + const upgrade = { + depType: 'pnpm.catalog.default', + depName: 'react', + newValue: '19.0.0', + }; + const pnpmWorkspaceYaml = codeBlock` + packages: + - pkg-a + + catalog: + react: &react 18.3.1 + react-dom: *react + `; + const testContent = npmUpdater.updateDependency({ + fileContent: pnpmWorkspaceYaml, + upgrade, + }); + expect(testContent).toEqual(codeBlock` + packages: + - pkg-a + + catalog: + react: &react 19.0.0 + react-dom: *react + `); + }); + + it('preserves whitespace with anchors', () => { + const upgrade = { + depType: 'pnpm.catalog.default', + depName: 'react', + newValue: '19.0.0', + }; + const pnpmWorkspaceYaml = codeBlock` + packages: + - pkg-a + + catalog: + react: &react 18.3.1 + `; + const testContent = npmUpdater.updateDependency({ + fileContent: pnpmWorkspaceYaml, + upgrade, + }); + expect(testContent).toEqual(codeBlock` + packages: + - pkg-a + + catalog: + react: &react 19.0.0 + `); + }); + + it('preserves quotation style with anchors', () => { + const upgrade = { + depType: 'pnpm.catalog.default', + depName: 'react', + newValue: '19.0.0', + }; + const pnpmWorkspaceYaml = codeBlock` + packages: + - pkg-a + + catalog: + react: &react "18.3.1" + `; + const testContent = npmUpdater.updateDependency({ + fileContent: pnpmWorkspaceYaml, + upgrade, + }); + expect(testContent).toEqual(codeBlock` + packages: + - pkg-a + + catalog: + react: &react "19.0.0" + `); + }); + + it('preserves formatting in flow style syntax', () => { + const upgrade = { + depType: 'pnpm.catalog.default', + depName: 'react', + newValue: '19.0.0', + }; + const pnpmWorkspaceYaml = codeBlock` + packages: + - pkg-a + + catalog: { + # This is a comment + "react": "18.3.1" + } + `; + const testContent = npmUpdater.updateDependency({ + fileContent: pnpmWorkspaceYaml, + upgrade, + }); + expect(testContent).toEqual(codeBlock` + packages: + - pkg-a + + catalog: { + # This is a comment + "react": "19.0.0" + } + `); + }); + + it('does not replace aliases in the value position', () => { + const upgrade = { + depType: 'pnpm.catalog.default', + depName: 'react', + newValue: '19.0.0', + }; + // In the general case, we do not know whether we should replace the anchor + // that an alias is resolved from. We leave this up to the user, e.g. via a + // Regex custom manager. + const pnpmWorkspaceYaml = codeBlock` + __deps: + react: &react 18.3.1 + + packages: + - pkg-a + + catalog: + react: *react + react-dom: *react + `; + const testContent = npmUpdater.updateDependency({ + fileContent: pnpmWorkspaceYaml, + upgrade, + }); + expect(testContent).toBeNull(); + }); + + it('does not replace aliases in the key position', () => { + const upgrade = { + depType: 'pnpm.catalog.default', + depName: 'react', + newName: 'react-x', + }; + const pnpmWorkspaceYaml = codeBlock` + __vars: + &r react: "" + + packages: + - pkg-a + + catalog: + *r: 18.0.0 + `; + const testContent = npmUpdater.updateDependency({ + fileContent: pnpmWorkspaceYaml, + upgrade, + }); + expect(testContent).toBeNull(); + }); +}); diff --git a/lib/modules/manager/npm/update/dependency/pnpm.ts b/lib/modules/manager/npm/update/dependency/pnpm.ts new file mode 100644 index 00000000000000..56ecbb03a45df9 --- /dev/null +++ b/lib/modules/manager/npm/update/dependency/pnpm.ts @@ -0,0 +1,157 @@ +import is from '@sindresorhus/is'; +import type { Document } from 'yaml'; +import { CST, isCollection, isPair, isScalar, parseDocument } from 'yaml'; +import { logger } from '../../../../../logger'; +import type { UpdateDependencyConfig } from '../../../types'; +import { PnpmCatalogsSchema } from '../../schema'; +import { getNewGitValue, getNewNpmAliasValue } from './common'; + +export function updatePnpmCatalogDependency({ + fileContent, + upgrade, +}: UpdateDependencyConfig): string | null { + const { depType, managerData, depName } = upgrade; + + const catalogName = depType?.split('.').at(-1); + + // istanbul ignore if + if (!is.string(catalogName)) { + logger.error( + 'No catalogName was found; this is likely an extraction error.', + ); + return null; + } + + let { newValue } = upgrade; + + newValue = getNewGitValue(upgrade) ?? newValue; + newValue = getNewNpmAliasValue(newValue, upgrade) ?? newValue; + + logger.trace( + `npm.updatePnpmCatalogDependency(): ${depType}:${managerData?.catalogName}.${depName} = ${newValue}`, + ); + + let document; + let parsedContents; + + try { + // In order to preserve the original formatting as much as possible, we want + // manipulate the CST directly. Using the AST (the result of parseDocument) + // does not guarantee that formatting would be the same after + // stringification. However, the CST is more annoying to query for certain + // values. Thus, we use both an annotated AST and a JS representation; the + // former for manipulation, and the latter for querying/validation. + document = parseDocument(fileContent, { keepSourceTokens: true }); + parsedContents = PnpmCatalogsSchema.parse(document.toJS()); + } catch (err) { + logger.debug({ err }, 'Could not parse pnpm-workspace YAML file.'); + return null; + } + + // In pnpm-workspace.yaml, the default catalog can be either `catalog` or + // `catalog.default`, but not both (pnpm throws outright with a config error). + // Thus, we must check which entry is being used, to reference it from / set + // it in the right place. + const usesImplicitDefaultCatalog = parsedContents.catalog !== undefined; + + const oldVersion = + catalogName === 'default' && usesImplicitDefaultCatalog + ? parsedContents.catalog?.[depName!] + : parsedContents.catalogs?.[catalogName]?.[depName!]; + + if (oldVersion === newValue) { + logger.trace('Version is already updated'); + return fileContent; + } + + // Update the value + const path = getDepPath({ + depName: depName!, + catalogName, + usesImplicitDefaultCatalog, + }); + + const modifiedDocument = changeDependencyIn(document, path, { + newValue, + newName: upgrade.newName, + }); + + if (!modifiedDocument) { + // Case where we are explicitly unable to substitute the key/value, for + // example if the value was an alias. + return null; + } + + // istanbul ignore if: this should not happen in practice, but we must satisfy th etypes + if (!modifiedDocument.contents?.srcToken) { + return null; + } + + return CST.stringify(modifiedDocument.contents.srcToken); +} + +/** + * Change the scalar name and/or value of a collection item in a YAML document, + * while keeping formatting consistent. Mutates the given document. + */ +function changeDependencyIn( + document: Document, + path: string[], + { newName, newValue }: { newName?: string; newValue?: string }, +): Document | null { + const parentPath = path.slice(0, -1); + const relevantItemKey = path.at(-1); + + const parentNode = document.getIn(parentPath); + + if (!parentNode || !isCollection(parentNode)) { + return null; + } + + const relevantNode = parentNode.items.find( + (item) => + isPair(item) && isScalar(item.key) && item.key.value === relevantItemKey, + ); + + if (!relevantNode || !isPair(relevantNode)) { + return null; + } + + if (newName) { + // istanbul ignore if: the try..catch block above already throws if a key is an alias + if (!CST.isScalar(relevantNode.srcToken?.key)) { + return null; + } + CST.setScalarValue(relevantNode.srcToken.key, newName); + } + + if (newValue) { + // We only support scalar values when substituting. This explicitly avoids + // substituting aliases, since those can be resolved from a shared location, + // and replacing either the referrent anchor or the alias would be wrong in + // the general case. We leave this up to the user, e.g. via a Regex custom + // manager. + if (!CST.isScalar(relevantNode.srcToken?.value)) { + return null; + } + CST.setScalarValue(relevantNode.srcToken.value, newValue); + } + + return document; +} + +function getDepPath({ + catalogName, + depName, + usesImplicitDefaultCatalog, +}: { + usesImplicitDefaultCatalog: boolean; + catalogName: string; + depName: string; +}): string[] { + if (catalogName === 'default' && usesImplicitDefaultCatalog) { + return ['catalog', depName]; + } else { + return ['catalogs', catalogName, depName]; + } +} diff --git a/lib/modules/manager/npm/update/locked-dependency/package-lock/index.ts b/lib/modules/manager/npm/update/locked-dependency/package-lock/index.ts index 5e95a770667f67..b9419d3c63e082 100644 --- a/lib/modules/manager/npm/update/locked-dependency/package-lock/index.ts +++ b/lib/modules/manager/npm/update/locked-dependency/package-lock/index.ts @@ -48,7 +48,7 @@ export async function updateLockedDependency( currentVersion, ); if (lockedDeps.some((dep) => dep.bundled)) { - logger.info( + logger.debug( `Package ${depName}@${currentVersion} is bundled and cannot be updated`, ); return { status: 'update-failed' }; diff --git a/lib/modules/manager/npm/update/package-version/index.ts b/lib/modules/manager/npm/update/package-version/index.ts index d40c34be61b408..03f70cfb76ac2c 100644 --- a/lib/modules/manager/npm/update/package-version/index.ts +++ b/lib/modules/manager/npm/update/package-version/index.ts @@ -35,7 +35,7 @@ export function bumpPackageVersion( parsedContent.optionalDependencies?.[mirrorPackage] ?? parsedContent.peerDependencies?.[mirrorPackage]; if (!newPjVersion) { - logger.warn('bumpVersion mirror package not found: ' + mirrorPackage); + logger.warn({ mirrorPackage }, 'bumpVersion mirror package not found'); return { bumpedContent }; } } else { diff --git a/lib/modules/manager/nuget/__fixtures__/sample.csproj b/lib/modules/manager/nuget/__fixtures__/sample.csproj index eae6a29972ccc2..36f5a469eb9fe7 100644 --- a/lib/modules/manager/nuget/__fixtures__/sample.csproj +++ b/lib/modules/manager/nuget/__fixtures__/sample.csproj @@ -3,6 +3,7 @@ netcoreapp1.1 0.1.0 + 4.5.0 @@ -11,9 +12,9 @@ - - - + + + diff --git a/lib/modules/manager/nuget/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/nuget/__snapshots__/extract.spec.ts.snap index 141706ed5a1658..60fcf38b3c7145 100644 --- a/lib/modules/manager/nuget/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/nuget/__snapshots__/extract.spec.ts.snap @@ -124,22 +124,24 @@ exports[`modules/manager/nuget/extract extractPackageFile() extracts all depende "depType": "nuget", }, { - "currentValue": "1.1.2", "datasource": "nuget", "depName": "Microsoft.AspNetCore.Hosting", "depType": "nuget", + "skipReason": "invalid-version", }, { - "currentValue": "4.1.0", + "currentValue": "4.5.0", "datasource": "nuget", "depName": "Autofac.Extensions.DependencyInjection", "depType": "nuget", + "sharedVariableName": "AutofacVersion", }, { "currentValue": "4.5.0", "datasource": "nuget", "depName": "Autofac", "depType": "nuget", + "sharedVariableName": "AutofacVersion", }, ] `; diff --git a/lib/modules/manager/nuget/artifacts.spec.ts b/lib/modules/manager/nuget/artifacts.spec.ts index 4dfbbc4a04de05..33a7465d63028b 100644 --- a/lib/modules/manager/nuget/artifacts.spec.ts +++ b/lib/modules/manager/nuget/artifacts.spec.ts @@ -15,7 +15,7 @@ jest.mock('../../../util/host-rules', () => mockDeep()); jest.mock('../../../util/git'); jest.mock('./util'); -const { getDefaultRegistries } = mocked(util); +const { getDefaultRegistries, findGlobalJson } = mocked(util); process.env.CONTAINERBASE = 'true'; @@ -230,12 +230,14 @@ describe('modules/manager/nuget/artifacts', () => { fs.getLocalFiles.mockResolvedValueOnce({ 'packages.lock.json': 'New packages.lock.json', }); + + findGlobalJson.mockResolvedValueOnce({ sdk: { version: '7.0.100' } }); expect( await nuget.updateArtifacts({ packageFileName: 'project.csproj', updatedDeps: [{ depName: 'dep' }], newPackageFileContent: '{}', - config: { ...config, constraints: { dotnet: '7.0.100' } }, + config, }), ).toEqual([ { diff --git a/lib/modules/manager/nuget/artifacts.ts b/lib/modules/manager/nuget/artifacts.ts index c60e04262e6363..88dcdd92e89471 100644 --- a/lib/modules/manager/nuget/artifacts.ts +++ b/lib/modules/manager/nuget/artifacts.ts @@ -25,7 +25,11 @@ import { NUGET_CENTRAL_FILE, getDependentPackageFiles, } from './package-tree'; -import { getConfiguredRegistries, getDefaultRegistries } from './util'; +import { + findGlobalJson, + getConfiguredRegistries, + getDefaultRegistries, +} from './util'; async function createCachedNuGetConfigFile( nugetCacheDir: string, @@ -55,6 +59,9 @@ async function runDotnetRestore( packageFileName, ); + const dotnetVersion = + config.constraints?.dotnet ?? + (await findGlobalJson(packageFileName))?.sdk?.version; const execOptions: ExecOptions = { docker: {}, userConfiguredEnv: config.env, @@ -62,9 +69,7 @@ async function runDotnetRestore( NUGET_PACKAGES: join(nugetCacheDir, 'packages'), MSBUILDDISABLENODEREUSE: '1', }, - toolConstraints: [ - { toolName: 'dotnet', constraint: config.constraints?.dotnet }, - ], + toolConstraints: [{ toolName: 'dotnet', constraint: dotnetVersion }], }; const cmds = [ diff --git a/lib/modules/manager/nuget/extract.spec.ts b/lib/modules/manager/nuget/extract.spec.ts index 592ff57f668989..cc73215022b96e 100644 --- a/lib/modules/manager/nuget/extract.spec.ts +++ b/lib/modules/manager/nuget/extract.spec.ts @@ -175,6 +175,7 @@ describe('modules/manager/nuget/extract', () => { autoReplaceStringTemplate: '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', depName: 'mcr.microsoft.com/dotnet/runtime', + packageName: 'mcr.microsoft.com/dotnet/runtime', depType: 'docker', datasource: 'docker', currentValue: '7.0.10', @@ -200,6 +201,7 @@ describe('modules/manager/nuget/extract', () => { autoReplaceStringTemplate: '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', depName: 'mcr.microsoft.com/dotnet/runtime', + packageName: 'mcr.microsoft.com/dotnet/runtime', depType: 'docker', datasource: 'docker', currentValue: '7.0.10', diff --git a/lib/modules/manager/nuget/extract.ts b/lib/modules/manager/nuget/extract.ts index 3b405f684335b4..2b578d58582bd4 100644 --- a/lib/modules/manager/nuget/extract.ts +++ b/lib/modules/manager/nuget/extract.ts @@ -1,9 +1,8 @@ import is from '@sindresorhus/is'; -import type { XmlElement, XmlNode } from 'xmldoc'; -import { XmlDocument } from 'xmldoc'; +import type { XmlNode } from 'xmldoc'; +import { XmlDocument, XmlElement } from 'xmldoc'; import { logger } from '../../../logger'; import { getSiblingFileName, localPathExists } from '../../../util/fs'; -import { hasKey } from '../../../util/object'; import { regEx } from '../../../util/regex'; import { NugetDatasource } from '../../datasource/nuget'; import { getDep } from '../dockerfile/extract'; @@ -37,12 +36,13 @@ const elemNames = new Set([ 'GlobalPackageReference', ]); -function isXmlElem(node: XmlNode): boolean { - return hasKey('name', node); +function isXmlElem(node: XmlNode): node is XmlElement { + return node instanceof XmlElement; } function extractDepsFromXml(xmlNode: XmlDocument): NugetPackageDependency[] { const results: NugetPackageDependency[] = []; + const vars = new Map(); const todo: XmlElement[] = [xmlNode]; while (todo.length) { const child = todo.pop()!; @@ -54,10 +54,9 @@ function extractDepsFromXml(xmlNode: XmlDocument): NugetPackageDependency[] { if (is.nonEmptyStringAndNotWhitespace(depName)) { results.push({ ...dep, depName, depType: 'docker' }); } - } - - if (elemNames.has(name)) { + } else if (elemNames.has(name)) { const depName = attr?.Include || attr?.Update; + if (!depName) { continue; } @@ -79,6 +78,23 @@ function extractDepsFromXml(xmlNode: XmlDocument): NugetPackageDependency[] { dep.skipReason = 'invalid-version'; } + let sharedVariableName: string | undefined; + + currentValue = currentValue + ?.trim() + ?.replace(/^\$\((\w+)\)$/, (match, key) => { + const val = vars.get(key); + if (val) { + sharedVariableName = key; + return val; + } + return match; + }); + + if (sharedVariableName) { + dep.sharedVariableName = sharedVariableName; + } + currentValue = checkVersion .exec(currentValue) ?.groups?.currentValue?.trim(); @@ -116,8 +132,21 @@ function extractDepsFromXml(xmlNode: XmlDocument): NugetPackageDependency[] { }); } } + + const propertyGroup = child.childNamed('PropertyGroup'); + if (propertyGroup) { + for (const propChild of propertyGroup.children) { + if (isXmlElem(propChild)) { + const { name, val } = propChild; + if (!['Version', 'TargetFramework'].includes(name)) { + vars.set(name, val); + } + } + } + } } - todo.push(...(child.children.filter(isXmlElem) as XmlElement[])); + + todo.push(...child.children.filter(isXmlElem)); } } return results; diff --git a/lib/modules/manager/nuget/extract/global-manifest.ts b/lib/modules/manager/nuget/extract/global-manifest.ts index ae9c6e9f99c042..d7e36b7f66c8ea 100644 --- a/lib/modules/manager/nuget/extract/global-manifest.ts +++ b/lib/modules/manager/nuget/extract/global-manifest.ts @@ -2,11 +2,8 @@ import { logger } from '../../../../logger'; import { DotnetVersionDatasource } from '../../../datasource/dotnet-version'; import { NugetDatasource } from '../../../datasource/nuget'; import type { PackageDependency, PackageFileContent } from '../../types'; -import type { - MsbuildGlobalManifest, - NugetPackageDependency, - Registry, -} from '../types'; +import { GlobalJson } from '../schema'; +import type { NugetPackageDependency, Registry } from '../types'; import { applyRegistries } from '../util'; export function extractMsbuildGlobalManifest( @@ -15,10 +12,10 @@ export function extractMsbuildGlobalManifest( registries: Registry[] | undefined, ): PackageFileContent | null { const deps: PackageDependency[] = []; - let manifest: MsbuildGlobalManifest; + let manifest: GlobalJson; let extractedConstraints: Record | undefined; try { - manifest = JSON.parse(content); + manifest = GlobalJson.parse(content); } catch { logger.debug({ packageFile }, `Invalid JSON`); return null; diff --git a/lib/modules/manager/nuget/schema.ts b/lib/modules/manager/nuget/schema.ts new file mode 100644 index 00000000000000..c71dabc94a1fc1 --- /dev/null +++ b/lib/modules/manager/nuget/schema.ts @@ -0,0 +1,63 @@ +import { z } from 'zod'; +import { Jsonc } from '../../../util/schema-utils'; + +/** + * The roll-forward policy to use when selecting an SDK version, either as a fallback when a specific SDK version is missing or as a directive to use a later version. A version must be specified with a rollForward value, unless you're setting it to latestMajor. The default roll forward behavior is determined by the matching rules. + * + * https://learn.microsoft.com/de-de/dotnet/core/tools/global-json#rollforward + */ +const RollForwardSchema = z.enum([ + 'patch', + 'feature', + 'minor', + 'major', + 'latestPatch', + 'latestFeature', + 'latestMinor', + 'latestMajor', + 'disable', +]); +export type RollForward = z.infer; + +/** + * global.json schema + * + * https://learn.microsoft.com/en-us/dotnet/core/tools/global-json#allowprerelease + */ +export const GlobalJsonSchema = z.object({ + /** + * Specifies information about the .NET SDK to select. + */ + sdk: z + .object({ + /** + * The version of the .NET SDK to use. + * + * https://learn.microsoft.com/de-de/dotnet/core/tools/global-json#version + */ + version: z.string().optional(), + /** + * The roll-forward policy to use when selecting an SDK version, either as a fallback when a specific SDK version is missing or as a directive to use a later version. A version must be specified with a rollForward value, unless you're setting it to latestMajor. The default roll forward behavior is determined by the matching rules. + * + * https://learn.microsoft.com/de-de/dotnet/core/tools/global-json#rollforward + */ + rollForward: RollForwardSchema.optional(), + /** + * Indicates whether the SDK resolver should consider prerelease versions when selecting the SDK version to use. + * + * https://learn.microsoft.com/de-de/dotnet/core/tools/global-json#allowprerelease + */ + allowPrerelease: z.boolean().optional(), + }) + .optional(), + + /** + * Lets you control the project SDK version in one place rather than in each individual project. For more information, see How project SDKs are resolved. + * + * https://learn.microsoft.com/de-de/dotnet/core/tools/global-json#msbuild-sdks + */ + 'msbuild-sdks': z.record(z.string()).optional(), +}); + +export const GlobalJson = Jsonc.pipe(GlobalJsonSchema); +export type GlobalJson = z.infer; diff --git a/lib/modules/manager/nuget/types.ts b/lib/modules/manager/nuget/types.ts index 0eef41beaf9885..9d34b776f14fb8 100644 --- a/lib/modules/manager/nuget/types.ts +++ b/lib/modules/manager/nuget/types.ts @@ -17,17 +17,6 @@ export interface Registry { readonly name?: string; sourceMappedPackagePatterns?: string[]; } - -export interface MsbuildGlobalManifest { - readonly sdk?: MsbuildSdk; - readonly 'msbuild-sdks'?: Record; -} - -export interface MsbuildSdk { - readonly version: string; - readonly rollForward: string; -} - export interface ProjectFile { readonly isLeaf: boolean; readonly name: string; diff --git a/lib/modules/manager/nuget/util.spec.ts b/lib/modules/manager/nuget/util.spec.ts index 0956e2014c0383..a1a01dec1040d1 100644 --- a/lib/modules/manager/nuget/util.spec.ts +++ b/lib/modules/manager/nuget/util.spec.ts @@ -3,7 +3,12 @@ import { XmlDocument } from 'xmldoc'; import { fs } from '../../../../test/util'; import type { Registry } from './types'; import { bumpPackageVersion } from './update'; -import { applyRegistries, findVersion, getConfiguredRegistries } from './util'; +import { + applyRegistries, + findGlobalJson, + findVersion, + getConfiguredRegistries, +} from './util'; jest.mock('../../../util/fs'); @@ -340,4 +345,34 @@ describe('modules/manager/nuget/util', () => { }); }); }); + + describe('findGlobalJson', () => { + it('not found', async () => { + fs.findLocalSiblingOrParent.mockResolvedValueOnce(null); + const globalJson = await findGlobalJson('project.csproj'); + expect(globalJson).toBeNull(); + }); + + it('no content', async () => { + fs.findLocalSiblingOrParent.mockResolvedValueOnce('global.json'); + const globalJson = await findGlobalJson('project.csproj'); + expect(globalJson).toBeNull(); + }); + + it('fails to parse', async () => { + fs.findLocalSiblingOrParent.mockResolvedValueOnce('global.json'); + fs.readLocalFile.mockResolvedValueOnce('{'); + const globalJson = await findGlobalJson('project.csproj'); + expect(globalJson).toBeNull(); + }); + + it('parses', async () => { + fs.findLocalSiblingOrParent.mockResolvedValueOnce('global.json'); + fs.readLocalFile.mockResolvedValueOnce( + '{ /* This is comment */ "sdk": { "version": "5.0.100" }, "some": true }', + ); + const globalJson = await findGlobalJson('project.csproj'); + expect(globalJson).toEqual({ sdk: { version: '5.0.100' } }); + }); + }); }); diff --git a/lib/modules/manager/nuget/util.ts b/lib/modules/manager/nuget/util.ts index dd210357d3a863..3f04f4318e758b 100644 --- a/lib/modules/manager/nuget/util.ts +++ b/lib/modules/manager/nuget/util.ts @@ -2,10 +2,15 @@ import upath from 'upath'; import type { XmlElement } from 'xmldoc'; import { XmlDocument } from 'xmldoc'; import { logger } from '../../../logger'; -import { findUpLocal, readLocalFile } from '../../../util/fs'; +import { + findLocalSiblingOrParent, + findUpLocal, + readLocalFile, +} from '../../../util/fs'; import { minimatch } from '../../../util/minimatch'; import { regEx } from '../../../util/regex'; import { nugetOrg } from '../../datasource/nuget'; +import { GlobalJson } from './schema'; import type { NugetPackageDependency, Registry } from './types'; export async function readFileAsXmlDocument( @@ -207,3 +212,31 @@ function sortPatterns( return a[0].localeCompare(b[0]) * -1; } + +export async function findGlobalJson( + packageFile: string, +): Promise { + const globalJsonPath = await findLocalSiblingOrParent( + packageFile, + 'global.json', + ); + if (!globalJsonPath) { + return null; + } + + const content = await readLocalFile(globalJsonPath, 'utf8'); + if (!content) { + logger.debug({ packageFile, globalJsonPath }, 'Failed to read global.json'); + return null; + } + + const result = await GlobalJson.safeParseAsync(content); + if (!result.success) { + logger.debug( + { packageFile, globalJsonPath, err: result.error }, + 'Failed to parse global.json', + ); + return null; + } + return result.data; +} diff --git a/lib/modules/manager/pep621/processors/uv.ts b/lib/modules/manager/pep621/processors/uv.ts index d7de0dc1195b32..637ef35df54f1f 100644 --- a/lib/modules/manager/pep621/processors/uv.ts +++ b/lib/modules/manager/pep621/processors/uv.ts @@ -11,7 +11,7 @@ import { find } from '../../../../util/host-rules'; import { Result } from '../../../../util/result'; import { parseUrl } from '../../../../util/url'; import { PypiDatasource } from '../../../datasource/pypi'; -import { getGoogleAuthTokenRaw } from '../../../datasource/util'; +import { getGoogleAuthHostRule } from '../../../datasource/util'; import type { PackageDependency, UpdateArtifact, @@ -265,12 +265,9 @@ async function getUsernamePassword( } if (url.hostname.endsWith('.pkg.dev')) { - const accessToken = await getGoogleAuthTokenRaw(); - if (accessToken) { - return { - username: 'oauth2accesstoken', - password: accessToken, - }; + const hostRule = await getGoogleAuthHostRule(); + if (hostRule) { + return hostRule; } else { logger.once.debug({ url }, 'Could not get Google access token'); } diff --git a/lib/modules/manager/pep621/utils.spec.ts b/lib/modules/manager/pep621/utils.spec.ts index 4d1e6edbcb0ce2..80d2ca16d8d9f2 100644 --- a/lib/modules/manager/pep621/utils.spec.ts +++ b/lib/modules/manager/pep621/utils.spec.ts @@ -10,6 +10,8 @@ describe('modules/manager/pep621/utils', () => { ${null} | ${false} | ${undefined} | ${undefined} | ${undefined} | ${undefined} ${'blinker'} | ${true} | ${'blinker'} | ${undefined} | ${undefined} | ${undefined} ${'packaging==20.0.0'} | ${true} | ${'packaging'} | ${'==20.0.0'} | ${undefined} | ${undefined} + ${'packaging (==20.0.0)'} | ${true} | ${'packaging'} | ${'==20.0.0'} | ${undefined} | ${undefined} + ${'packaging (==20.0.0); python_version < "3.8"'} | ${true} | ${'packaging'} | ${'==20.0.0'} | ${undefined} | ${'python_version < "3.8"'} ${'packaging>=20.9,!=22.0'} | ${true} | ${'packaging'} | ${'>=20.9,!=22.0'} | ${undefined} | ${undefined} ${'cachecontrol[filecache]>=0.12.11'} | ${true} | ${'cachecontrol'} | ${'>=0.12.11'} | ${['filecache']} | ${undefined} ${'private-depB[extra1, extra2]~=2.4'} | ${true} | ${'private-depB'} | ${'~=2.4'} | ${['extra1', 'extra2']} | ${undefined} diff --git a/lib/modules/manager/pep621/utils.ts b/lib/modules/manager/pep621/utils.ts index 1a396064922174..6df1b79ae4e018 100644 --- a/lib/modules/manager/pep621/utils.ts +++ b/lib/modules/manager/pep621/utils.ts @@ -43,8 +43,16 @@ export function parsePEP508( packageName: regExpExec.groups.packageName, }; if (is.nonEmptyString(regExpExec.groups.currentValue)) { - result.currentValue = regExpExec.groups.currentValue; + if ( + regExpExec.groups.currentValue.startsWith('(') && + regExpExec.groups.currentValue.endsWith(')') + ) { + result.currentValue = regExpExec.groups.currentValue.slice(1, -1).trim(); + } else { + result.currentValue = regExpExec.groups.currentValue; + } } + if (is.nonEmptyString(regExpExec.groups.marker)) { result.marker = regExpExec.groups.marker; } diff --git a/lib/modules/manager/pip-compile/common.ts b/lib/modules/manager/pip-compile/common.ts index 6ec0b128fc9f56..5a2126198900be 100644 --- a/lib/modules/manager/pip-compile/common.ts +++ b/lib/modules/manager/pip-compile/common.ts @@ -216,7 +216,8 @@ export function extractPythonVersion( const match = pythonVersionRegex.exec(content); if (match?.groups === undefined) { logger.warn( - `pip-compile: failed to extract Python version from header in ${fileName} ${content}`, + { fileName, content }, + 'pip-compile: failed to extract Python version from header in file', ); return undefined; } diff --git a/lib/modules/manager/pip-compile/extract.spec.ts b/lib/modules/manager/pip-compile/extract.spec.ts index fd70448a9a3e1e..9fe1b1dbb65230 100644 --- a/lib/modules/manager/pip-compile/extract.spec.ts +++ b/lib/modules/manager/pip-compile/extract.spec.ts @@ -473,7 +473,8 @@ describe('modules/manager/pip-compile/extract', () => { 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', + { packageFile: '1.in', requirementsFile: 'reqs-no-headers.txt' }, + 'pip-compile: Package file references a file which does not appear to be a requirements file managed by pip-compile', ); }); @@ -494,7 +495,8 @@ describe('modules/manager/pip-compile/extract', () => { 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', + { packageFile: '1.in', requirementsFile: 'unmanaged-file.txt' }, + 'pip-compile: Package file references a file which does not appear to be a requirements file managed by pip-compile', ); }); diff --git a/lib/modules/manager/pip-compile/extract.ts b/lib/modules/manager/pip-compile/extract.ts index 1ef63fc65d9ca0..d040d676015699 100644 --- a/lib/modules/manager/pip-compile/extract.ts +++ b/lib/modules/manager/pip-compile/extract.ts @@ -66,7 +66,10 @@ export async function extractAllPackageFiles( compileArgs = extractHeaderCommand(fileContent, fileMatch); compileDir = inferCommandExecDir(fileMatch, compileArgs.outputFile); } catch (error) { - logger.warn({ fileMatch }, `pip-compile: ${error.message}`); + logger.warn( + { fileMatch, errorMessage: error.message }, + 'pip-compile error', + ); continue; } lockFileArgs.set(fileMatch, compileArgs); @@ -212,7 +215,8 @@ export async function extractAllPackageFiles( } if (!sourceFiles) { logger.warn( - `pip-compile: ${packageFile.packageFile} references ${reqFile} which does not appear to be a requirements file managed by pip-compile`, + { packageFile: packageFile.packageFile, requirementsFile: reqFile }, + 'pip-compile: Package file references a file which does not appear to be a requirements file managed by pip-compile', ); continue; } diff --git a/lib/modules/manager/pipenv/artifacts.ts b/lib/modules/manager/pipenv/artifacts.ts index 8d2760ce357aa6..41ae62a97864b1 100644 --- a/lib/modules/manager/pipenv/artifacts.ts +++ b/lib/modules/manager/pipenv/artifacts.ts @@ -72,7 +72,8 @@ export function addExtraEnvVariable( extraEnv[environmentVariableName] !== environmentValue ) { logger.warn( - `Possible misconfiguration, ${environmentVariableName} is already set to a different value`, + { envVar: environmentVariableName }, + 'Possible misconfiguration, environment variable already set to a different value', ); } extraEnv[environmentVariableName] = environmentValue; diff --git a/lib/modules/manager/poetry/artifacts.spec.ts b/lib/modules/manager/poetry/artifacts.spec.ts index f7db72a8c48838..18c970100612e1 100644 --- a/lib/modules/manager/poetry/artifacts.spec.ts +++ b/lib/modules/manager/poetry/artifacts.spec.ts @@ -239,6 +239,7 @@ describe('modules/manager/poetry/artifacts', () => { getAccessToken: jest.fn().mockResolvedValue('some-token'), })), ); + hostRules.find.mockReturnValue({}); const updatedDeps = [{ depName: 'dep1' }]; expect( await updateArtifacts({ diff --git a/lib/modules/manager/poetry/artifacts.ts b/lib/modules/manager/poetry/artifacts.ts index ec77248288828c..697fabe35136f1 100644 --- a/lib/modules/manager/poetry/artifacts.ts +++ b/lib/modules/manager/poetry/artifacts.ts @@ -19,7 +19,7 @@ import { Result } from '../../../util/result'; import { parse as parseToml } from '../../../util/toml'; import { parseUrl } from '../../../util/url'; import { PypiDatasource } from '../../datasource/pypi'; -import { getGoogleAuthTokenRaw } from '../../datasource/util'; +import { getGoogleAuthHostRule } from '../../datasource/util'; import type { UpdateArtifact, UpdateArtifactsResult } from '../types'; import { Lockfile, PoetrySchemaToml } from './schema'; import type { PoetryFile, PoetrySource } from './types'; @@ -120,7 +120,7 @@ function getPoetrySources(content: string, fileName: string): PoetrySource[] { async function getMatchingHostRule(url: string | undefined): Promise { const scopedMatch = find({ hostType: PypiDatasource.id, url }); const hostRule = is.nonEmptyObject(scopedMatch) ? scopedMatch : find({ url }); - if (hostRule) { + if (hostRule && Object.keys(hostRule).length !== 0) { return hostRule; } @@ -131,12 +131,9 @@ async function getMatchingHostRule(url: string | undefined): Promise { } if (parsedUrl.hostname.endsWith('.pkg.dev')) { - const accessToken = await getGoogleAuthTokenRaw(); - if (accessToken) { - return { - username: 'oauth2accesstoken', - password: accessToken, - }; + const hostRule = await getGoogleAuthHostRule(); + if (hostRule && Object.keys(hostRule).length !== 0) { + return hostRule; } logger.once.debug(`Could not get Google access token (url=${url})`); } diff --git a/lib/modules/manager/poetry/schema.ts b/lib/modules/manager/poetry/schema.ts index 6e650c21b2d0fc..eb4df77fca1858 100644 --- a/lib/modules/manager/poetry/schema.ts +++ b/lib/modules/manager/poetry/schema.ts @@ -195,9 +195,9 @@ export const PoetryGroupDependencies = LooseRecord( .transform(({ dependencies }) => dependencies), ).transform((record) => { const deps: PackageDependency[] = []; - for (const [groupName, group] of Object.entries(record)) { - for (const dep of Object.values(group)) { - dep.depType = groupName; + for (const [name, val] of Object.entries(record)) { + for (const dep of Object.values(val)) { + dep.depType = name; deps.push(dep); } } diff --git a/lib/modules/manager/pre-commit/__fixtures__/complex.pre-commit-config.yaml b/lib/modules/manager/pre-commit/__fixtures__/complex.pre-commit-config.yaml index 1b1eab5ea00dfd..ff2c733c80d560 100644 --- a/lib/modules/manager/pre-commit/__fixtures__/complex.pre-commit-config.yaml +++ b/lib/modules/manager/pre-commit/__fixtures__/complex.pre-commit-config.yaml @@ -52,6 +52,11 @@ repos: - id: prettier exclude: ^notebooks rev: v2.1.2 + - repo: ssh://git@github.com/pre-commit/pre-commit-hooks + # should allow explicit ssh protocol + hooks: + - id: check-merge-conflict + rev: v5.0.0 - repo: some_invalid_url # case with invlalid url. rev: v1.0.0 diff --git a/lib/modules/manager/pre-commit/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/pre-commit/__snapshots__/extract.spec.ts.snap index 7dea84085efd04..986fcae3dc1367 100644 --- a/lib/modules/manager/pre-commit/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/pre-commit/__snapshots__/extract.spec.ts.snap @@ -73,6 +73,13 @@ exports[`modules/manager/pre-commit/extract extractPackageFile() extracts from c "depType": "repository", "packageName": "prettier/pre-commit", }, + { + "currentValue": "v5.0.0", + "datasource": "github-tags", + "depName": "pre-commit/pre-commit-hooks", + "depType": "repository", + "packageName": "pre-commit/pre-commit-hooks", + }, { "currentValue": "v1.0.0", "datasource": undefined, diff --git a/lib/modules/manager/pre-commit/extract.spec.ts b/lib/modules/manager/pre-commit/extract.spec.ts index 5eaf7426a522a1..011f7b242b0d05 100644 --- a/lib/modules/manager/pre-commit/extract.spec.ts +++ b/lib/modules/manager/pre-commit/extract.spec.ts @@ -105,6 +105,7 @@ describe('modules/manager/pre-commit/extract', () => { }, { depName: 'prettier/pre-commit', currentValue: 'v2.1.2' }, { depName: 'prettier/pre-commit', currentValue: 'v2.1.2' }, + { depName: 'pre-commit/pre-commit-hooks', currentValue: 'v5.0.0' }, { skipReason: 'invalid-url' }, ], }); diff --git a/lib/modules/manager/pre-commit/extract.ts b/lib/modules/manager/pre-commit/extract.ts index a127861cd65c24..3fa7f690984a6a 100644 --- a/lib/modules/manager/pre-commit/extract.ts +++ b/lib/modules/manager/pre-commit/extract.ts @@ -96,6 +96,8 @@ function extractDependency( regEx('^git@(?[^:]+):(?\\S*)'), // This split "git://github.com/pre-commit/pre-commit-hooks" -> "github.com" "pre-commit/pre-commit-hooks" regEx(/^git:\/\/(?[^/]+)\/(?\S*)/), + // This splits "ssh://git@github.com/pre-commit/pre-commit-hooks" -> "github.com" "pre-commit/pre-commit-hooks" + regEx(/^ssh:\/\/git@(?[^/]+)\/(?\S*)/), ]; for (const urlMatcher of urlMatchers) { const match = urlMatcher.exec(repository); diff --git a/lib/modules/manager/sbt/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/sbt/__snapshots__/extract.spec.ts.snap index e369d665da0fc2..87d51705bf4989 100644 --- a/lib/modules/manager/sbt/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/sbt/__snapshots__/extract.spec.ts.snap @@ -22,9 +22,9 @@ exports[`modules/manager/sbt/extract extractPackageFile() extract deps from nati "currentValue": "1.2.3", "datasource": "sbt-package", "depName": "com.abc:abc", - "groupName": "abcVersion", "packageName": "com.abc:abc", "registryUrls": [], + "sharedVariableName": "abcVersion", "variableName": "abcVersion", }, ], @@ -57,36 +57,36 @@ exports[`modules/manager/sbt/extract extractPackageFile() extract deps from nati "currentValue": "1.2.3", "datasource": "sbt-package", "depName": "com.abc:abc", - "groupName": "abcVersion", "packageName": "com.abc:abc", "registryUrls": [], + "sharedVariableName": "abcVersion", "variableName": "abcVersion", }, { "currentValue": "1.2.3", "datasource": "sbt-package", "depName": "com.abc:abc-a", - "groupName": "abcVersion", "packageName": "com.abc:abc-a", "registryUrls": [], + "sharedVariableName": "abcVersion", "variableName": "abcVersion", }, { "currentValue": "1.2.3", "datasource": "sbt-package", "depName": "com.abc:abc-b", - "groupName": "abcVersion", "packageName": "com.abc:abc-b", "registryUrls": [], + "sharedVariableName": "abcVersion", "variableName": "abcVersion", }, { "currentValue": "1.2.3", "datasource": "sbt-package", "depName": "com.abc:abc-c", - "groupName": "abcVersion", "packageName": "com.abc:abc-c", "registryUrls": [], + "sharedVariableName": "abcVersion", "variableName": "abcVersion", }, ], @@ -240,7 +240,6 @@ exports[`modules/manager/sbt/extract extractPackageFile() extracts deps for gene "datasource": "sbt-package", "depName": "org.example:grault", "depType": "Test", - "groupName": "versionExample", "packageName": "org.example:grault", "registryUrls": [ "https://example.com/repos/1/", @@ -249,6 +248,7 @@ exports[`modules/manager/sbt/extract extractPackageFile() extracts deps for gene "https://example.com/repos/4/", "https://example.com/repos/5/", ], + "sharedVariableName": "versionExample", "variableName": "versionExample", }, { @@ -401,7 +401,6 @@ exports[`modules/manager/sbt/extract extractPackageFile() extracts deps when sca "datasource": "sbt-package", "depName": "org.example:grault", "depType": "Test", - "groupName": "versionExample", "packageName": "org.example:grault", "registryUrls": [ "https://example.com/repos/1/", @@ -410,6 +409,7 @@ exports[`modules/manager/sbt/extract extractPackageFile() extracts deps when sca "https://example.com/repos/4/", "https://example.com/repos/5/", ], + "sharedVariableName": "versionExample", "variableName": "versionExample", }, { diff --git a/lib/modules/manager/sbt/extract.spec.ts b/lib/modules/manager/sbt/extract.spec.ts index 7410b282ae382c..03f3cbf232c2f7 100644 --- a/lib/modules/manager/sbt/extract.spec.ts +++ b/lib/modules/manager/sbt/extract.spec.ts @@ -116,7 +116,7 @@ describe('modules/manager/sbt/extract', () => { deps: [ { currentValue: '1.2.3', - groupName: 'version', + sharedVariableName: 'version', }, ], }); @@ -137,7 +137,7 @@ describe('modules/manager/sbt/extract', () => { datasource: 'sbt-plugin', depName: 'com.github.gseitz:sbt-release', depType: 'plugin', - groupName: 'sbtReleaseVersion', + sharedVariableName: 'sbtReleaseVersion', packageName: 'com.github.gseitz:sbt-release', registryUrls: [], variableName: 'sbtReleaseVersion', diff --git a/lib/modules/manager/sbt/extract.ts b/lib/modules/manager/sbt/extract.ts index fd5a9173769dd0..80bfc399ee0acf 100644 --- a/lib/modules/manager/sbt/extract.ts +++ b/lib/modules/manager/sbt/extract.ts @@ -218,7 +218,7 @@ function depHandler(ctx: Ctx): Ctx { } if (variableName) { - dep.groupName = variableName; + dep.sharedVariableName = variableName; dep.variableName = variableName; } diff --git a/lib/modules/manager/sveltos/extract.ts b/lib/modules/manager/sveltos/extract.ts index 35d39cceef5245..cf87c449c0a2a4 100644 --- a/lib/modules/manager/sveltos/extract.ts +++ b/lib/modules/manager/sveltos/extract.ts @@ -54,7 +54,7 @@ function processHelmCharts( const image = trimTrailingSlash(removeOCIPrefix(source.repositoryURL)); dep.datasource = DockerDatasource.id; - dep.packageName = getDep(image, false, registryAliases).depName; + dep.packageName = getDep(image, false, registryAliases).packageName; } else { dep.packageName = removeRepositoryName( source.repositoryName, diff --git a/lib/modules/manager/tekton/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/tekton/__snapshots__/extract.spec.ts.snap index 8f4a5bb10596e9..a6151f53ed0435 100644 --- a/lib/modules/manager/tekton/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/tekton/__snapshots__/extract.spec.ts.snap @@ -10,6 +10,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "gcr.io/tekton-releases/catalog/upstream/pipeline", "depType": "tekton-bundle", + "packageName": "gcr.io/tekton-releases/catalog/upstream/pipeline", "replaceString": "gcr.io/tekton-releases/catalog/upstream/pipeline:1.0@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", }, { @@ -19,6 +20,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "gcr.io/tekton-releases/catalog/upstream/pipeline-finally", "depType": "tekton-bundle", + "packageName": "gcr.io/tekton-releases/catalog/upstream/pipeline-finally", "replaceString": "gcr.io/tekton-releases/catalog/upstream/pipeline-finally:1.0@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", }, { @@ -28,6 +30,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "gcr.io/tekton-releases/catalog/upstream/pipeline-resolver", "depType": "tekton-bundle", + "packageName": "gcr.io/tekton-releases/catalog/upstream/pipeline-resolver", "replaceString": "gcr.io/tekton-releases/catalog/upstream/pipeline-resolver:1.0@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", }, { @@ -37,6 +40,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "gcr.io/tekton-releases/catalog/upstream/list-pipeline", "depType": "tekton-bundle", + "packageName": "gcr.io/tekton-releases/catalog/upstream/list-pipeline", "replaceString": "gcr.io/tekton-releases/catalog/upstream/list-pipeline", }, { @@ -46,6 +50,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "gcr.io/tekton-releases/catalog/upstream/list-pipeline-run", "depType": "tekton-bundle", + "packageName": "gcr.io/tekton-releases/catalog/upstream/list-pipeline-run", "replaceString": "gcr.io/tekton-releases/catalog/upstream/list-pipeline-run", }, { @@ -55,6 +60,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "gcr.io/tekton-releases/catalog/upstream/list-task-run", "depType": "tekton-bundle", + "packageName": "gcr.io/tekton-releases/catalog/upstream/list-task-run", "replaceString": "gcr.io/tekton-releases/catalog/upstream/list-task-run", }, { @@ -64,6 +70,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "gcr.io/tekton-releases/catalog/upstream/trigger-template-task-run", "depType": "tekton-bundle", + "packageName": "gcr.io/tekton-releases/catalog/upstream/trigger-template-task-run", "replaceString": "gcr.io/tekton-releases/catalog/upstream/trigger-template-task-run", }, { @@ -73,6 +80,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "gcr.io/tekton-releases/catalog/upstream/trigger-template-task-run-resolver", "depType": "tekton-bundle", + "packageName": "gcr.io/tekton-releases/catalog/upstream/trigger-template-task-run-resolver", "replaceString": "gcr.io/tekton-releases/catalog/upstream/trigger-template-task-run-resolver", }, { @@ -82,6 +90,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "gcr.io/tekton-releases/catalog/upstream/trigger-template-pipeline-run", "depType": "tekton-bundle", + "packageName": "gcr.io/tekton-releases/catalog/upstream/trigger-template-pipeline-run", "replaceString": "gcr.io/tekton-releases/catalog/upstream/trigger-template-pipeline-run", }, { @@ -91,6 +100,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "gcr.io/tekton-releases/catalog/upstream/trigger-template-pipeline-run-resolver", "depType": "tekton-bundle", + "packageName": "gcr.io/tekton-releases/catalog/upstream/trigger-template-pipeline-run-resolver", "replaceString": "gcr.io/tekton-releases/catalog/upstream/trigger-template-pipeline-run-resolver", }, { @@ -100,6 +110,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "gcr.io/tekton-releases/catalog/upstream/task-run", "depType": "tekton-bundle", + "packageName": "gcr.io/tekton-releases/catalog/upstream/task-run", "replaceString": "gcr.io/tekton-releases/catalog/upstream/task-run", }, { @@ -109,6 +120,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "gcr.io/tekton-releases/catalog/upstream/task-run-resolver", "depType": "tekton-bundle", + "packageName": "gcr.io/tekton-releases/catalog/upstream/task-run-resolver", "replaceString": "gcr.io/tekton-releases/catalog/upstream/task-run-resolver", }, { @@ -118,6 +130,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "gcr.io/tekton-releases/catalog/upstream/pipeline-run", "depType": "tekton-bundle", + "packageName": "gcr.io/tekton-releases/catalog/upstream/pipeline-run", "replaceString": "gcr.io/tekton-releases/catalog/upstream/pipeline-run", }, { @@ -127,6 +140,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "gcr.io/tekton-releases/catalog/upstream/pipeline-run-resolver", "depType": "tekton-bundle", + "packageName": "gcr.io/tekton-releases/catalog/upstream/pipeline-run-resolver", "replaceString": "gcr.io/tekton-releases/catalog/upstream/pipeline-run-resolver", }, { @@ -136,6 +150,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "gcr.io/tekton-releases/catalog/upstream/inline-pipeline", "depType": "tekton-bundle", + "packageName": "gcr.io/tekton-releases/catalog/upstream/inline-pipeline", "replaceString": "gcr.io/tekton-releases/catalog/upstream/inline-pipeline", }, { @@ -157,6 +172,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "gcr.io/tekton-releases/catalog/upstream/pipeline-resolver", "depType": "tekton-bundle", + "packageName": "gcr.io/tekton-releases/catalog/upstream/pipeline-resolver", "replaceString": "gcr.io/tekton-releases/catalog/upstream/pipeline-resolver:1.0@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", }, { @@ -166,6 +182,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "gcr.io/tekton-releases/catalog/upstream/pipeline-resolver", "depType": "tekton-bundle", + "packageName": "gcr.io/tekton-releases/catalog/upstream/pipeline-resolver", "replaceString": "gcr.io/tekton-releases/catalog/upstream/pipeline-resolver:1.0@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", }, { @@ -175,6 +192,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "example.io/taskrun/spec/taskSpec/steps/0/image", "depType": "tekton-step-image", + "packageName": "example.io/taskrun/spec/taskSpec/steps/0/image", "replaceString": "example.io/taskrun/spec/taskSpec/steps/0/image", }, { @@ -184,6 +202,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "example.io/taskrun/spec/taskSpec/sidecars/0/image", "depType": "tekton-step-image", + "packageName": "example.io/taskrun/spec/taskSpec/sidecars/0/image", "replaceString": "example.io/taskrun/spec/taskSpec/sidecars/0/image", }, { @@ -193,6 +212,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "example.io/taskrun/spec/taskSpec/stepTemplate/image", "depType": "tekton-step-image", + "packageName": "example.io/taskrun/spec/taskSpec/stepTemplate/image", "replaceString": "example.io/taskrun/spec/taskSpec/stepTemplate/image", }, { @@ -202,6 +222,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "example.io/task/spec/steps/0/image", "depType": "tekton-step-image", + "packageName": "example.io/task/spec/steps/0/image", "replaceString": "example.io/task/spec/steps/0/image", }, { @@ -211,6 +232,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "example.io/task/spec/sidecars/0/image", "depType": "tekton-step-image", + "packageName": "example.io/task/spec/sidecars/0/image", "replaceString": "example.io/task/spec/sidecars/0/image", }, { @@ -220,6 +242,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "example.io/task/spec/stepTemplate/image", "depType": "tekton-step-image", + "packageName": "example.io/task/spec/stepTemplate/image", "replaceString": "example.io/task/spec/stepTemplate/image", }, { @@ -229,6 +252,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "example.com/pipeline/spec/tasks/0/taskSpec/steps/0/image", "depType": "tekton-step-image", + "packageName": "example.com/pipeline/spec/tasks/0/taskSpec/steps/0/image", "replaceString": "example.com/pipeline/spec/tasks/0/taskSpec/steps/0/image", }, { @@ -238,6 +262,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "example.com/pipeline/spec/tasks/0/taskSpec/sidecars/0/image", "depType": "tekton-step-image", + "packageName": "example.com/pipeline/spec/tasks/0/taskSpec/sidecars/0/image", "replaceString": "example.com/pipeline/spec/tasks/0/taskSpec/sidecars/0/image", }, { @@ -247,6 +272,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "example.com/pipeline/spec/tasks/0/taskSpec/stepTemplate/image", "depType": "tekton-step-image", + "packageName": "example.com/pipeline/spec/tasks/0/taskSpec/stepTemplate/image", "replaceString": "example.com/pipeline/spec/tasks/0/taskSpec/stepTemplate/image", }, { @@ -256,6 +282,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "example.com/pipeline/spec/finally/0/taskSpec/steps/0/image", "depType": "tekton-step-image", + "packageName": "example.com/pipeline/spec/finally/0/taskSpec/steps/0/image", "replaceString": "example.com/pipeline/spec/finally/0/taskSpec/steps/0/image", }, { @@ -265,6 +292,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "example.com/pipeline/spec/finally/0/taskSpec/sidecars/0/image", "depType": "tekton-step-image", + "packageName": "example.com/pipeline/spec/finally/0/taskSpec/sidecars/0/image", "replaceString": "example.com/pipeline/spec/finally/0/taskSpec/sidecars/0/image", }, { @@ -274,6 +302,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "example.com/pipeline/spec/finally/0/taskSpec/stepTemplate/image", "depType": "tekton-step-image", + "packageName": "example.com/pipeline/spec/finally/0/taskSpec/stepTemplate/image", "replaceString": "example.com/pipeline/spec/finally/0/taskSpec/stepTemplate/image", }, { @@ -283,6 +312,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "example.com/pipelinerun/spec/pipelineSpec/tasks/0/taskSpec/steps/0/image", "depType": "tekton-step-image", + "packageName": "example.com/pipelinerun/spec/pipelineSpec/tasks/0/taskSpec/steps/0/image", "replaceString": "example.com/pipelinerun/spec/pipelineSpec/tasks/0/taskSpec/steps/0/image", }, { @@ -292,6 +322,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "example.com/pipelinerun/spec/pipelineSpec/tasks/0/taskSpec/sidecars/0/image", "depType": "tekton-step-image", + "packageName": "example.com/pipelinerun/spec/pipelineSpec/tasks/0/taskSpec/sidecars/0/image", "replaceString": "example.com/pipelinerun/spec/pipelineSpec/tasks/0/taskSpec/sidecars/0/image", }, { @@ -301,6 +332,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "example.com/pipelinerun/spec/pipelineSpec/tasks/0/taskSpec/stepTemplate/image", "depType": "tekton-step-image", + "packageName": "example.com/pipelinerun/spec/pipelineSpec/tasks/0/taskSpec/stepTemplate/image", "replaceString": "example.com/pipelinerun/spec/pipelineSpec/tasks/0/taskSpec/stepTemplate/image", }, { @@ -310,6 +342,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "example.com/pipelinerun/spec/pipelineSpec/finally/0/taskSpec/steps/0/image", "depType": "tekton-step-image", + "packageName": "example.com/pipelinerun/spec/pipelineSpec/finally/0/taskSpec/steps/0/image", "replaceString": "example.com/pipelinerun/spec/pipelineSpec/finally/0/taskSpec/steps/0/image", }, { @@ -319,6 +352,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "example.com/pipelinerun/spec/pipelineSpec/finally/0/taskSpec/sidecars/0/image", "depType": "tekton-step-image", + "packageName": "example.com/pipelinerun/spec/pipelineSpec/finally/0/taskSpec/sidecars/0/image", "replaceString": "example.com/pipelinerun/spec/pipelineSpec/finally/0/taskSpec/sidecars/0/image", }, { @@ -328,6 +362,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "example.com/pipelinerun/spec/pipelineSpec/finally/0/taskSpec/stepTemplate/image", "depType": "tekton-step-image", + "packageName": "example.com/pipelinerun/spec/pipelineSpec/finally/0/taskSpec/stepTemplate/image", "replaceString": "example.com/pipelinerun/spec/pipelineSpec/finally/0/taskSpec/stepTemplate/image", }, { @@ -337,6 +372,7 @@ exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from "datasource": "docker", "depName": "example.com/triggertemplate/spec/resourcetemplates/0/taskrun/spec/taskSpec/steps/0/image", "depType": "tekton-step-image", + "packageName": "example.com/triggertemplate/spec/resourcetemplates/0/taskrun/spec/taskSpec/steps/0/image", "replaceString": "example.com/triggertemplate/spec/resourcetemplates/0/taskrun/spec/taskSpec/steps/0/image", }, ], diff --git a/lib/modules/manager/terraform/extract.spec.ts b/lib/modules/manager/terraform/extract.spec.ts index 273537bc332c53..e0eb63b5aedae6 100644 --- a/lib/modules/manager/terraform/extract.spec.ts +++ b/lib/modules/manager/terraform/extract.spec.ts @@ -456,7 +456,8 @@ describe('modules/manager/terraform/extract', () => { 'hub.proxy.test/bitnami/nginx:{{#if newValue}}{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', currentValue: '1.24.0', datasource: 'docker', - depName: 'index.docker.io/bitnami/nginx', + depName: 'hub.proxy.test/bitnami/nginx', + packageName: 'index.docker.io/bitnami/nginx', depType: 'docker_image', replaceString: 'hub.proxy.test/bitnami/nginx:1.24.0', }, diff --git a/lib/modules/manager/terraform/extractors/resources/helm-release.ts b/lib/modules/manager/terraform/extractors/resources/helm-release.ts index 835ae6ab2380e9..11d8bc944c11eb 100644 --- a/lib/modules/manager/terraform/extractors/resources/helm-release.ts +++ b/lib/modules/manager/terraform/extractors/resources/helm-release.ts @@ -79,7 +79,7 @@ export class HelmReleaseExtractor extends DependencyExtractor { config: ExtractConfig, dep: PackageDependency, ): void { - const { depName: packageName, datasource } = getDep( + const { packageName, datasource } = getDep( depName, false, config.registryAliases, diff --git a/lib/modules/manager/types.ts b/lib/modules/manager/types.ts index 2ce51aa306a0d9..0937191789a0b6 100644 --- a/lib/modules/manager/types.ts +++ b/lib/modules/manager/types.ts @@ -14,6 +14,7 @@ import type { } from '../../types'; import type { FileChange } from '../../util/git/types'; import type { MergeConfidence } from '../../util/merge-confidence/types'; +import type { Timestamp } from '../../util/timestamp'; import type { CustomExtractConfig } from './custom/types'; export type MaybePromise = T | Promise; @@ -103,9 +104,10 @@ export interface LookupUpdate { userStrings?: Record; checksumUrl?: string; downloadUrl?: string; - releaseTimestamp?: any; + releaseTimestamp?: Timestamp; newVersionAgeInDays?: number; registryUrl?: string; + libYears?: number; } /** @@ -119,7 +121,7 @@ export interface PackageDependency> depName?: string; depType?: string; fileReplacePosition?: number; - groupName?: string; + sharedVariableName?: string; lineNumber?: number; packageName?: string; target?: string; @@ -144,6 +146,7 @@ export interface PackageDependency> digestOneAndOnly?: boolean; fixedVersion?: string; currentVersion?: string; + currentVersionTimestamp?: string; lockedVersion?: string; propSource?: string; registryUrls?: string[] | null; diff --git a/lib/modules/manager/vendir/extract.ts b/lib/modules/manager/vendir/extract.ts index c711882839d4e1..13626e8240aa1c 100644 --- a/lib/modules/manager/vendir/extract.ts +++ b/lib/modules/manager/vendir/extract.ts @@ -32,7 +32,6 @@ export function extractHelmChart( return { ...dep, depName: helmChart.name, - packageName: dep.depName, depType: 'HelmChart', // https://github.com/helm/helm/issues/10312 // https://github.com/helm/helm/issues/10678 diff --git a/lib/modules/manager/woodpecker/extract.spec.ts b/lib/modules/manager/woodpecker/extract.spec.ts index 9149b024d0a1ed..182e1144309768 100644 --- a/lib/modules/manager/woodpecker/extract.spec.ts +++ b/lib/modules/manager/woodpecker/extract.spec.ts @@ -24,6 +24,7 @@ describe('modules/manager/woodpecker/extract', () => { deps: [ { depName: 'woodpeckerci/plugin-git', + packageName: 'woodpeckerci/plugin-git', currentValue: '2.0.3', currentDigest: undefined, replaceString: 'woodpeckerci/plugin-git:2.0.3', @@ -33,6 +34,7 @@ describe('modules/manager/woodpecker/extract', () => { }, { depName: 'quay.io/something/redis', + packageName: 'quay.io/something/redis', currentValue: 'alpine', currentDigest: undefined, replaceString: 'quay.io/something/redis:alpine', @@ -42,6 +44,7 @@ describe('modules/manager/woodpecker/extract', () => { }, { depName: 'node', + packageName: 'node', currentValue: '10.0.0', currentDigest: undefined, replaceString: 'node:10.0.0', @@ -51,6 +54,7 @@ describe('modules/manager/woodpecker/extract', () => { }, { depName: 'postgres', + packageName: 'postgres', currentValue: '9.4.0', currentDigest: undefined, replaceString: 'postgres:9.4.0', @@ -60,6 +64,7 @@ describe('modules/manager/woodpecker/extract', () => { }, { depName: 'dockersamples/examplevotingapp_vote', + packageName: 'dockersamples/examplevotingapp_vote', currentValue: 'before', currentDigest: undefined, replaceString: 'dockersamples/examplevotingapp_vote:before', @@ -69,6 +74,7 @@ describe('modules/manager/woodpecker/extract', () => { }, { depName: 'dockersamples/examplevotingapp_result', + packageName: 'dockersamples/examplevotingapp_result', currentValue: 'before', currentDigest: undefined, replaceString: 'dockersamples/examplevotingapp_result:before', @@ -78,6 +84,7 @@ describe('modules/manager/woodpecker/extract', () => { }, { depName: 'dockersamples/examplevotingapp_worker', + packageName: 'dockersamples/examplevotingapp_worker', currentValue: undefined, currentDigest: undefined, replaceString: 'dockersamples/examplevotingapp_worker', @@ -87,6 +94,7 @@ describe('modules/manager/woodpecker/extract', () => { }, { depName: 'dockersamples/visualizer', + packageName: 'dockersamples/visualizer', currentValue: 'stable', currentDigest: undefined, replaceString: 'dockersamples/visualizer:stable', @@ -96,6 +104,7 @@ describe('modules/manager/woodpecker/extract', () => { }, { depName: 'app-local-debug', + packageName: 'app-local-debug', currentValue: undefined, currentDigest: undefined, replaceString: 'app-local-debug', @@ -105,6 +114,7 @@ describe('modules/manager/woodpecker/extract', () => { }, { depName: 'postgres', + packageName: 'postgres', currentValue: '9.5.0', currentDigest: undefined, replaceString: 'postgres:9.5.0', @@ -138,7 +148,8 @@ describe('modules/manager/woodpecker/extract', () => { currentDigest: undefined, currentValue: '0.0.1', datasource: 'docker', - depName: 'my-quay-mirror.registry.com/nginx', + depName: 'quay.io/nginx', + packageName: 'my-quay-mirror.registry.com/nginx', replaceString: 'quay.io/nginx:0.0.1', }, ], @@ -168,6 +179,7 @@ describe('modules/manager/woodpecker/extract', () => { currentValue: '0.0.1', datasource: 'docker', depName: 'quay.io/nginx', + packageName: 'quay.io/nginx', replaceString: 'quay.io/nginx:0.0.1', }, ], @@ -197,7 +209,8 @@ describe('modules/manager/woodpecker/extract', () => { currentDigest: undefined, currentValue: '0.0.1', datasource: 'docker', - depName: 'my-quay-mirror.registry.com/nginx', + depName: 'quay.io/nginx', + packageName: 'my-quay-mirror.registry.com/nginx', replaceString: 'quay.io/nginx:0.0.1', }, ], @@ -218,6 +231,7 @@ describe('modules/manager/woodpecker/extract', () => { deps: [ { depName: 'quay.io/something/redis', + packageName: 'quay.io/something/redis', currentValue: 'alpine', currentDigest: undefined, replaceString: 'quay.io/something/redis:alpine', @@ -247,6 +261,7 @@ describe('modules/manager/woodpecker/extract', () => { deps: [ { depName: 'woodpeckerci/plugin-git', + packageName: 'woodpeckerci/plugin-git', currentValue: 'latest', currentDigest: undefined, replaceString: 'woodpeckerci/plugin-git:latest', @@ -256,6 +271,7 @@ describe('modules/manager/woodpecker/extract', () => { }, { depName: 'quay.io/something/redis', + packageName: 'quay.io/something/redis', currentValue: 'alpine', currentDigest: undefined, replaceString: 'quay.io/something/redis:alpine', @@ -282,6 +298,7 @@ describe('modules/manager/woodpecker/extract', () => { deps: [ { depName: 'woodpeckerci/plugin-git', + packageName: 'woodpeckerci/plugin-git', currentValue: 'latest', currentDigest: undefined, replaceString: 'woodpeckerci/plugin-git:latest', diff --git a/lib/modules/platform/azure/azure-helper.ts b/lib/modules/platform/azure/azure-helper.ts index 3fb4602d5547f6..3c96fc94caf5a9 100644 --- a/lib/modules/platform/azure/azure-helper.ts +++ b/lib/modules/platform/azure/azure-helper.ts @@ -87,11 +87,11 @@ export async function getFile( const result = WrappedExceptionSchema.safeParse(fileContent); if (result.success) { if (result.data.typeKey === 'GitItemNotFoundException') { - logger.warn(`Unable to find file ${filePath}`); + logger.warn({ filePath }, 'Unable to find file'); return null; } if (result.data.typeKey === 'GitUnresolvableToCommitException') { - logger.warn(`Unable to find branch ${branchName}`); + logger.warn({ branchName }, 'Unable to find branch'); return null; } } diff --git a/lib/modules/platform/azure/index.ts b/lib/modules/platform/azure/index.ts index 37deb70b83b355..0fd6bb8ccefcf1 100644 --- a/lib/modules/platform/azure/index.ts +++ b/lib/modules/platform/azure/index.ts @@ -822,11 +822,13 @@ export async function mergePr({ if (!isClosed) { logger.warn( - { pullRequestId, status: pr.status }, - `Expected PR to have status ${ - PullRequestStatus[PullRequestStatus.Completed] - // TODO #22198 - }. However, it is ${PullRequestStatus[pr.status!]}.`, + { + pullRequestId, + status: pr.status, + expectedPRStatus: PullRequestStatus[PullRequestStatus.Completed], + actualPRStatus: PullRequestStatus[pr.status!], + }, + 'Expected PR to have completed status. However, the PR has a different status', ); } return true; diff --git a/lib/modules/platform/azure/util.spec.ts b/lib/modules/platform/azure/util.spec.ts index bfdea61b104c98..14c277e276dda1 100644 --- a/lib/modules/platform/azure/util.spec.ts +++ b/lib/modules/platform/azure/util.spec.ts @@ -170,7 +170,7 @@ describe('modules/platform/azure/util', () => { it('should return an error', () => { expect(() => getProjectAndRepo('prjName/myRepoName/blalba')).toThrow( Error( - `prjName/myRepoName/blalba can be only structured this way : 'repository' or 'projectName/repository'!`, + `Azure repository can be only structured this way : 'repository' or 'projectName/repository'!`, ), ); }); diff --git a/lib/modules/platform/azure/util.ts b/lib/modules/platform/azure/util.ts index 74ca2ab7d27f5a..ccc3b43d7da2a5 100644 --- a/lib/modules/platform/azure/util.ts +++ b/lib/modules/platform/azure/util.ts @@ -160,8 +160,8 @@ export function getProjectAndRepo(str: string): { repo: strSplit[1], }; } - const msg = `${str} can be only structured this way : 'repository' or 'projectName/repository'!`; - logger.error(msg); + const msg = `Azure repository can be only structured this way : 'repository' or 'projectName/repository'!`; + logger.warn({ repository: str }, msg); throw new Error(msg); } diff --git a/lib/modules/platform/bitbucket-server/index.ts b/lib/modules/platform/bitbucket-server/index.ts index 5978a3682a6cc1..7f1b2accebc809 100644 --- a/lib/modules/platform/bitbucket-server/index.ts +++ b/lib/modules/platform/bitbucket-server/index.ts @@ -116,7 +116,7 @@ export async function initPlatform({ bitbucketServerVersion = process.env.RENOVATE_X_PLATFORM_VERSION; } else { const { version } = ( - await bitbucketServerHttp.getJson<{ version: string }>( + await bitbucketServerHttp.getJsonUnchecked<{ version: string }>( `./rest/api/1.0/application-properties`, ) ).body; @@ -199,14 +199,13 @@ export async function getRawFile( const fileUrl = `./rest/api/1.0/projects/${project}/repos/${slug}/browse/${fileName}?limit=20000` + (branchOrTag ? '&at=' + branchOrTag : ''); - const res = await bitbucketServerHttp.getJson(fileUrl); + const res = await bitbucketServerHttp.getJsonUnchecked(fileUrl); const { isLastPage, lines, size } = res.body; if (isLastPage) { return lines.map(({ text }) => text).join('\n'); } - const msg = `The file is too big (${size}B)`; - logger.warn({ size }, msg); - throw new Error(msg); + logger.warn({ size }, 'The file is too big'); + throw new Error(`The file is too big (${size}B)`); } export async function getJsonFile( @@ -246,13 +245,13 @@ export async function initRepo({ try { const info = ( - await bitbucketServerHttp.getJson( + await bitbucketServerHttp.getJsonUnchecked( `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}`, ) ).body; config.owner = info.project.key; logger.debug(`${repository} owner = ${config.owner}`); - const branchRes = await bitbucketServerHttp.getJson( + const branchRes = await bitbucketServerHttp.getJsonUnchecked( `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/branches/default`, ); @@ -304,7 +303,7 @@ export async function getBranchForceRebase( _branchName: string, ): Promise { // https://docs.atlassian.com/bitbucket-server/rest/7.0.1/bitbucket-rest.html#idp342 - const res = await bitbucketServerHttp.getJson<{ + const res = await bitbucketServerHttp.getJsonUnchecked<{ mergeConfig: { defaultStrategy: { id: string } }; }>( `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/settings/pull-requests`, @@ -328,7 +327,7 @@ export async function getPr( return null; } - const res = await bitbucketServerHttp.getJson( + const res = await bitbucketServerHttp.getJsonUnchecked( `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${prNo}`, { memCache: !refreshCache }, ); @@ -459,7 +458,7 @@ async function getStatus( const branchCommit = git.getBranchCommit(branchName); return ( - await bitbucketServerHttp.getJson( + await bitbucketServerHttp.getJsonUnchecked( // TODO: types (#22198) `./rest/build-status/1.0/commits/stats/${branchCommit!}`, { memCache }, @@ -778,7 +777,7 @@ async function getCommentVersion( ): Promise { // GET /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/comments/{commentId} const { version } = ( - await bitbucketServerHttp.getJson<{ version: number }>( + await bitbucketServerHttp.getJsonUnchecked<{ version: number }>( `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${prNo}/comments/${commentId}`, ) ).body; @@ -914,13 +913,13 @@ export async function createPr({ if (platformPrOptions?.bbUseDefaultReviewers) { logger.debug(`fetching default reviewers`); const { id } = ( - await bitbucketServerHttp.getJson<{ id: number }>( + await bitbucketServerHttp.getJsonUnchecked<{ id: number }>( `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}`, ) ).body; const defReviewers = ( - await bitbucketServerHttp.getJson<{ name: string }[]>( + await bitbucketServerHttp.getJsonUnchecked<{ name: string }[]>( `./rest/default-reviewers/1.0/projects/${config.projectKey}/repos/${ config.repositorySlug }/reviewers?sourceRefId=refs/heads/${escapeHash( diff --git a/lib/modules/platform/bitbucket-server/utils.ts b/lib/modules/platform/bitbucket-server/utils.ts index 92099dafab02b6..8d51b2607d183d 100644 --- a/lib/modules/platform/bitbucket-server/utils.ts +++ b/lib/modules/platform/bitbucket-server/utils.ts @@ -68,7 +68,7 @@ function callApi( return bitbucketServerHttp.deleteJson(apiUrl, options); case 'get': default: - return bitbucketServerHttp.getJson(apiUrl, options); + return bitbucketServerHttp.getJsonUnchecked(apiUrl, options); } } diff --git a/lib/modules/platform/bitbucket/comments.ts b/lib/modules/platform/bitbucket/comments.ts index 2d91964a30d53f..98cfbf5e2c52bc 100644 --- a/lib/modules/platform/bitbucket/comments.ts +++ b/lib/modules/platform/bitbucket/comments.ts @@ -24,7 +24,7 @@ async function getComments( prNo: number, ): Promise { const comments = ( - await bitbucketHttp.getJson>( + await bitbucketHttp.getJsonUnchecked>( `/2.0/repositories/${config.repository}/pullrequests/${prNo}/comments`, { paginate: true, diff --git a/lib/modules/platform/bitbucket/index.ts b/lib/modules/platform/bitbucket/index.ts index f58265dad7aa8c..0d562c7b5d61f7 100644 --- a/lib/modules/platform/bitbucket/index.ts +++ b/lib/modules/platform/bitbucket/index.ts @@ -93,7 +93,7 @@ export async function initPlatform({ } try { const { uuid } = ( - await bitbucketHttp.getJson('/2.0/user', options) + await bitbucketHttp.getJsonUnchecked('/2.0/user', options) ).body; renovateUserUuid = uuid; } catch (err) { @@ -212,7 +212,7 @@ export async function initRepo({ if (bbUseDevelopmentBranch) { // Fetch Bitbucket development branch const developmentBranch = ( - await bitbucketHttp.getJson( + await bitbucketHttp.getJsonUnchecked( `/2.0/repositories/${repository}/branching-model`, ) ).body.development?.branch?.name; @@ -304,7 +304,7 @@ export async function findPr({ if (includeOtherAuthors) { // PR might have been created by anyone, so don't use the cached Renovate PR list const prs = ( - await bitbucketHttp.getJson>( + await bitbucketHttp.getJsonUnchecked>( `/2.0/repositories/${config.repository}/pullrequests?q=source.branch.name="${branchName}"&state=open`, ) ).body.values; @@ -363,7 +363,7 @@ export async function findPr({ // Gets details for a PR export async function getPr(prNo: number): Promise { const pr = ( - await bitbucketHttp.getJson( + await bitbucketHttp.getJsonUnchecked( `/2.0/repositories/${config.repository}/pullrequests/${prNo}`, ) ).body; @@ -395,7 +395,7 @@ async function getBranchCommit( ): Promise { try { const branch = ( - await bitbucketHttp.getJson( + await bitbucketHttp.getJsonUnchecked( `/2.0/repositories/${config.repository}/refs/branches/${escapeHash( branchName, )}`, @@ -424,7 +424,7 @@ async function getStatus( ): Promise { const sha = await getBranchCommit(branchName); return ( - await bitbucketHttp.getJson>( + await bitbucketHttp.getJsonUnchecked>( `/2.0/repositories/${config.repository}/commit/${sha!}/statuses`, { paginate: true, @@ -531,7 +531,7 @@ async function findOpenIssues(title: string): Promise { const filter = encodeURIComponent(filters.join(' AND ')); return ( ( - await bitbucketHttp.getJson<{ values: BbIssue[] }>( + await bitbucketHttp.getJsonUnchecked<{ values: BbIssue[] }>( `/2.0/repositories/${config.repository}/issues?q=${filter}`, ) ).body.values || /* istanbul ignore next */ [] @@ -677,7 +677,7 @@ export async function getIssueList(): Promise { } const filter = encodeURIComponent(filters.join(' AND ')); const url = `/2.0/repositories/${config.repository}/issues?q=${filter}`; - const res = await bitbucketHttp.getJson<{ values: Issue[] }>(url, { + const res = await bitbucketHttp.getJsonUnchecked<{ values: Issue[] }>(url, { cacheProvider: repoCacheProvider, }); return res.body.values || []; @@ -788,7 +788,7 @@ async function sanitizeReviewers( // Validate that each previous PR reviewer account is still active for (const reviewer of reviewers) { const reviewerUser = ( - await bitbucketHttp.getJson( + await bitbucketHttp.getJsonUnchecked( `/2.0/users/${reviewer.uuid}`, { memCache: true }, ) @@ -881,7 +881,7 @@ export async function createPr({ if (platformPrOptions?.bbUseDefaultReviewers) { const reviewersResponse = ( - await bitbucketHttp.getJson>( + await bitbucketHttp.getJsonUnchecked>( `/2.0/repositories/${config.repository}/effective-default-reviewers`, { paginate: true, @@ -1019,7 +1019,7 @@ export async function updatePr({ logger.debug(`updatePr(${prNo}, ${title}, body)`); // Updating a PR in Bitbucket will clear the reviewers if reviewers is not present const pr = ( - await bitbucketHttp.getJson( + await bitbucketHttp.getJsonUnchecked( `/2.0/repositories/${config.repository}/pullrequests/${prNo}`, ) ).body; diff --git a/lib/modules/platform/bitbucket/pr-cache.ts b/lib/modules/platform/bitbucket/pr-cache.ts index b8e9000e3c3784..84e8fc4a31d3fe 100644 --- a/lib/modules/platform/bitbucket/pr-cache.ts +++ b/lib/modules/platform/bitbucket/pr-cache.ts @@ -147,7 +147,7 @@ export class BitbucketPrCache { pagelen: 50, cacheProvider: repoCacheProvider, }; - const res = await http.getJson>(url, opts); + const res = await http.getJsonUnchecked>(url, opts); const items = res.body.values; logger.debug(`Fetched ${items.length} PRs to sync with cache`); diff --git a/lib/modules/platform/gerrit/client.ts b/lib/modules/platform/gerrit/client.ts index fbc392765b418a..e058f0b703f791 100644 --- a/lib/modules/platform/gerrit/client.ts +++ b/lib/modules/platform/gerrit/client.ts @@ -30,7 +30,7 @@ class GerritClient { private gerritHttp = new GerritHttp(); async getRepos(): Promise { - const res = await this.gerritHttp.getJson( + const res = await this.gerritHttp.getJsonUnchecked( 'a/projects/?type=CODE&state=ACTIVE', {}, ); @@ -38,9 +38,10 @@ class GerritClient { } async getProjectInfo(repository: string): Promise { - const projectInfo = await this.gerritHttp.getJson( - `a/projects/${encodeURIComponent(repository)}`, - ); + const projectInfo = + await this.gerritHttp.getJsonUnchecked( + `a/projects/${encodeURIComponent(repository)}`, + ); if (projectInfo.body.state !== 'ACTIVE') { throw new Error(REPOSITORY_ARCHIVED); } @@ -48,7 +49,7 @@ class GerritClient { } async getBranchInfo(repository: string): Promise { - const branchInfo = await this.gerritHttp.getJson( + const branchInfo = await this.gerritHttp.getJsonUnchecked( `a/projects/${encodeURIComponent(repository)}/branches/HEAD`, ); return branchInfo.body; @@ -60,7 +61,7 @@ class GerritClient { refreshCache?: boolean, ): Promise { const filters = GerritClient.buildSearchFilters(repository, findPRConfig); - const changes = await this.gerritHttp.getJson( + const changes = await this.gerritHttp.getJsonUnchecked( `a/changes/?q=` + filters.join('+') + this.requestDetails.map((det) => `&o=${det}`).join(''), @@ -73,7 +74,7 @@ class GerritClient { } async getChange(changeNumber: number): Promise { - const changes = await this.gerritHttp.getJson( + const changes = await this.gerritHttp.getJsonUnchecked( `a/changes/${changeNumber}?` + this.requestDetails.map((det) => `o=${det}`).join('&'), ); @@ -81,9 +82,10 @@ class GerritClient { } async getMergeableInfo(change: GerritChange): Promise { - const mergeable = await this.gerritHttp.getJson( - `a/changes/${change._number}/revisions/current/mergeable`, - ); + const mergeable = + await this.gerritHttp.getJsonUnchecked( + `a/changes/${change._number}/revisions/current/mergeable`, + ); return mergeable.body; } @@ -99,10 +101,9 @@ class GerritClient { } async getMessages(changeNumber: number): Promise { - const messages = await this.gerritHttp.getJson( - `a/changes/${changeNumber}/messages`, - { memCache: false }, - ); + const messages = await this.gerritHttp.getJsonUnchecked< + GerritChangeMessageInfo[] + >(`a/changes/${changeNumber}/messages`, { memCache: false }); return messages.body; } diff --git a/lib/modules/platform/gerrit/scm.ts b/lib/modules/platform/gerrit/scm.ts index 887e77ab2461c7..1e95a2a55af44c 100644 --- a/lib/modules/platform/gerrit/scm.ts +++ b/lib/modules/platform/gerrit/scm.ts @@ -73,7 +73,8 @@ export class GerritScm extends DefaultGitScm { return !mergeInfo.mergeable; } else { logger.warn( - `There is no open change with branch=${branch} and baseBranch=${baseBranch}`, + { branch, baseBranch }, + 'There is no open change with this branch', ); return true; } diff --git a/lib/modules/platform/gitea/gitea-helper.spec.ts b/lib/modules/platform/gitea/gitea-helper.spec.ts index 9f06e6750abf06..e3680f6d11978d 100644 --- a/lib/modules/platform/gitea/gitea-helper.spec.ts +++ b/lib/modules/platform/gitea/gitea-helper.spec.ts @@ -685,37 +685,41 @@ describe('modules/platform/gitea/gitea-helper', () => { }); it('should properly determine worst commit status', async () => { - const statuses: { - status: CommitStatusType; - created_at: string; + const statuses: (Pick & { expected: CommitStatusType; - }[] = [ + })[] = [ { + id: 122, status: 'unknown', created_at: '2020-03-25T01:00:00Z', expected: 'unknown', }, { + id: 124, status: 'pending', created_at: '2020-03-25T03:00:00Z', expected: 'pending', }, { + id: 125, status: 'warning', created_at: '2020-03-25T04:00:00Z', expected: 'warning', }, { + id: 126, status: 'failure', created_at: '2020-03-25T05:00:00Z', expected: 'failure', }, { + id: 123, status: 'success', created_at: '2020-03-25T02:00:00Z', expected: 'failure', }, { + id: 127, status: 'success', created_at: '2020-03-25T06:00:00Z', expected: 'success', @@ -726,13 +730,13 @@ describe('modules/platform/gitea/gitea-helper', () => { { ...mockCommitStatus, status: 'unknown' }, ]; - for (const statusElem of statuses) { - const { status, expected } = statusElem; + for (const { id, status, expected, created_at } of statuses) { // Add current status ot list of commit statuses, then mock the API to return the whole list commitStatuses.push({ ...mockCommitStatus, + id, status, - created_at: statusElem.created_at, + created_at, }); httpMock .scope(baseUrl) diff --git a/lib/modules/platform/gitea/gitea-helper.ts b/lib/modules/platform/gitea/gitea-helper.ts index 2514faf3e74672..53e5a54abf4e0d 100644 --- a/lib/modules/platform/gitea/gitea-helper.ts +++ b/lib/modules/platform/gitea/gitea-helper.ts @@ -46,13 +46,16 @@ export async function getCurrentUser( options?: GiteaHttpOptions, ): Promise { const url = `${API_PATH}/user`; - const res = await giteaHttp.getJson(url, options); + const res = await giteaHttp.getJsonUnchecked(url, options); return res.body; } export async function getVersion(options?: GiteaHttpOptions): Promise { const url = `${API_PATH}/version`; - const res = await giteaHttp.getJson<{ version: string }>(url, options); + const res = await giteaHttp.getJsonUnchecked<{ version: string }>( + url, + options, + ); return res.body.version; } @@ -62,7 +65,7 @@ export async function searchRepos( ): Promise { const query = getQueryString(params); const url = `${API_PATH}/repos/search?${query}`; - const res = await giteaHttp.getJson(url, { + const res = await giteaHttp.getJsonUnchecked(url, { ...options, paginate: true, }); @@ -81,7 +84,7 @@ export async function orgListRepos( options?: GiteaHttpOptions, ): Promise { const url = `${API_PATH}/orgs/${organization}/repos`; - const res = await giteaHttp.getJson(url, { + const res = await giteaHttp.getJsonUnchecked(url, { ...options, paginate: true, }); @@ -94,7 +97,7 @@ export async function getRepo( options?: GiteaHttpOptions, ): Promise { const url = `${API_PATH}/repos/${repoPath}`; - const res = await giteaHttp.getJson(url, options); + const res = await giteaHttp.getJsonUnchecked(url, options); return res.body; } @@ -108,7 +111,7 @@ export async function getRepoContents( const url = `${API_PATH}/repos/${repoPath}/contents/${urlEscape( filePath, )}?${query}`; - const res = await giteaHttp.getJson(url, options); + const res = await giteaHttp.getJsonUnchecked(url, options); if (res.body.content) { res.body.contentString = Buffer.from(res.body.content, 'base64').toString(); @@ -176,7 +179,7 @@ export async function getPR( options?: GiteaHttpOptions, ): Promise { const url = `${API_PATH}/repos/${repoPath}/pulls/${idx}`; - const res = await giteaHttp.getJson(url, options); + const res = await giteaHttp.getJsonUnchecked(url, options); return res.body; } @@ -255,7 +258,7 @@ export async function searchIssues( ): Promise { const query = getQueryString({ ...params, type: 'issues' }); const url = `${API_PATH}/repos/${repoPath}/issues?${query}`; - const res = await giteaHttp.getJson(url, { + const res = await giteaHttp.getJsonUnchecked(url, { ...options, paginate: true, }); @@ -269,7 +272,7 @@ export async function getIssue( options?: GiteaHttpOptions, ): Promise { const url = `${API_PATH}/repos/${repoPath}/issues/${idx}`; - const res = await giteaHttp.getJson(url, options); + const res = await giteaHttp.getJsonUnchecked(url, options); return res.body; } @@ -278,7 +281,7 @@ export async function getRepoLabels( options?: GiteaHttpOptions, ): Promise { const url = `${API_PATH}/repos/${repoPath}/labels`; - const res = await giteaHttp.getJson(url, options); + const res = await giteaHttp.getJsonUnchecked(url, options); return res.body; } @@ -288,7 +291,7 @@ export async function getOrgLabels( options?: GiteaHttpOptions, ): Promise { const url = `${API_PATH}/orgs/${orgName}/labels`; - const res = await giteaHttp.getJson(url, options); + const res = await giteaHttp.getJsonUnchecked(url, options); return res.body; } @@ -350,7 +353,7 @@ export async function getComments( options?: GiteaHttpOptions, ): Promise { const url = `${API_PATH}/repos/${repoPath}/issues/${issue}/comments`; - const res = await giteaHttp.getJson(url, options); + const res = await giteaHttp.getJsonUnchecked(url, options); return res.body; } @@ -394,10 +397,7 @@ export const renovateToGiteaStatusMapping: Record< function filterStatus(data: CommitStatus[]): CommitStatus[] { const ret: Record = {}; for (const i of data) { - if ( - !ret[i.context] || - new Date(ret[i.context].created_at) < new Date(i.created_at) - ) { + if (!ret[i.context] || ret[i.context].id < i.id) { ret[i.context] = i; } } @@ -412,19 +412,20 @@ export async function getCombinedCommitStatus( const url = `${API_PATH}/repos/${repoPath}/commits/${urlEscape( branchName, )}/statuses`; - const res = await giteaHttp.getJson(url, { + const res = await giteaHttp.getJsonUnchecked(url, { ...options, paginate: true, }); let worstState = 0; - for (const cs of filterStatus(res.body)) { + const statuses = filterStatus(res.body); + for (const cs of statuses) { worstState = Math.max(worstState, commitStatusStates.indexOf(cs.status)); } return { worstStatus: commitStatusStates[worstState], - statuses: res.body, + statuses, }; } @@ -434,7 +435,7 @@ export async function getBranch( options?: GiteaHttpOptions, ): Promise { const url = `${API_PATH}/repos/${repoPath}/branches/${urlEscape(branchName)}`; - const res = await giteaHttp.getJson(url, options); + const res = await giteaHttp.getJsonUnchecked(url, options); return res.body; } diff --git a/lib/modules/platform/gitea/index.spec.ts b/lib/modules/platform/gitea/index.spec.ts index 84031f86974876..53ce4256adff6b 100644 --- a/lib/modules/platform/gitea/index.spec.ts +++ b/lib/modules/platform/gitea/index.spec.ts @@ -9,6 +9,7 @@ import { REPOSITORY_CHANGED, REPOSITORY_EMPTY, REPOSITORY_MIRRORED, + TEMPORARY_ERROR, } from '../../../constants/error-messages'; import type { logger as _logger } from '../../../logger'; import type * as _git from '../../../util/git'; @@ -1309,6 +1310,18 @@ describe('modules/platform/gitea/index', () => { expect(res).toBeNull(); }); + + it('should throw temporary error for null pull request', async () => { + const scope = httpMock + .scope('https://gitea.com/api/v1') + .get('/repos/some/repo/pulls') + .query({ state: 'all', sort: 'recentupdate' }) + .reply(200, [null]); // TODO: 404 should be handled + await initFakePlatform(scope); + await initFakeRepo(scope); + + await expect(gitea.getPr(42)).rejects.toThrow(TEMPORARY_ERROR); + }); }); describe('findPr', () => { @@ -1666,7 +1679,7 @@ describe('modules/platform/gitea/index', () => { .reply(200, mockNewPR) .post('/repos/some/repo/pulls/42/merge') .reply(200); - await initFakePlatform(scope, '1.17.0'); + await initFakePlatform(scope, '1.24.0'); await initFakeRepo(scope); const res = await gitea.createPr({ @@ -1684,16 +1697,14 @@ describe('modules/platform/gitea/index', () => { expect(mergePR).toHaveBeenCalled(); }); - it('should use platform automerge on forgejo v7', async () => { + it('should not 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); + .reply(200, mockNewPR); await initFakePlatform(scope, '7.0.0-dev-2136-f075579c95+gitea-1.22.0'); await initFakeRepo(scope); @@ -1709,19 +1720,17 @@ describe('modules/platform/gitea/index', () => { number: 42, title: 'pr-title', }); - expect(mergePR).toHaveBeenCalled(); + expect(mergePR).not.toHaveBeenCalled(); }); - it('should use platform automerge on forgejo v7 LTS', async () => { + it('should not 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); + .reply(200, mockNewPR); await initFakePlatform(scope, '7.0.0+LTS-gitea-1.22.0'); await initFakeRepo(scope); @@ -1737,7 +1746,7 @@ describe('modules/platform/gitea/index', () => { number: 42, title: 'pr-title', }); - expect(mergePR).toHaveBeenCalled(); + expect(mergePR).not.toHaveBeenCalled(); }); it('continues on platform automerge error', async () => { @@ -1748,7 +1757,7 @@ describe('modules/platform/gitea/index', () => { .reply(200, mockNewPR) .post('/repos/some/repo/pulls/42/merge') .replyWithError('unknown error'); - await initFakePlatform(scope, '1.17.0'); + await initFakePlatform(scope, '1.24.0'); await initFakeRepo(scope); const res = await gitea.createPr({ @@ -1792,7 +1801,7 @@ describe('modules/platform/gitea/index', () => { }); expect(logger.debug).toHaveBeenCalledWith( expect.objectContaining({ prNumber: 42 }), - 'Gitea-native automerge: not supported on this version of Gitea. Use 1.17.0 or newer.', + 'Gitea-native automerge: not supported on this version of Gitea. Use 1.24.0 or newer.', ); }); @@ -1804,7 +1813,7 @@ describe('modules/platform/gitea/index', () => { .reply(200, mockNewPR) .post('/repos/some/repo/pulls/42/merge') .reply(200); - await initFakePlatform(scope, '1.17.0'); + await initFakePlatform(scope, '10.0.0+gitea-1.22.0'); await initFakeRepo(scope); const res = await gitea.createPr({ @@ -1843,7 +1852,7 @@ describe('modules/platform/gitea/index', () => { Do: prMergeStrategy, merge_when_checks_succeed: true, }); - await initFakePlatform(scope, '1.17.0'); + await initFakePlatform(scope, '1.24.0'); await initFakeRepo(scope); const res = await gitea.createPr({ diff --git a/lib/modules/platform/gitea/index.ts b/lib/modules/platform/gitea/index.ts index 31450e6c4480ff..369edaad369b7b 100644 --- a/lib/modules/platform/gitea/index.ts +++ b/lib/modules/platform/gitea/index.ts @@ -214,9 +214,6 @@ const platform: Platform = { 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) { @@ -551,13 +548,18 @@ const platform: Platform = { }); if (platformPrOptions?.usePlatformAutomerge) { - if (semver.gte(defaults.version, '1.17.0')) { + // Only Gitea v1.24.0+ and Forgejo v10.0.0+ support delete_branch_after_merge. + // This is required to not have undesired behavior when renovate finds existing branches on next run. + if ( + semver.gte(defaults.version, defaults.isForgejo ? '10.0.0' : '1.24.0') + ) { try { await helper.mergePR(config.repository, gpr.number, { Do: getMergeMethod(platformPrOptions?.automergeStrategy) ?? config.mergeMethod, merge_when_checks_succeed: true, + delete_branch_after_merge: true, }); logger.debug( @@ -573,7 +575,7 @@ const platform: Platform = { } else { logger.debug( { prNumber: gpr.number }, - 'Gitea-native automerge: not supported on this version of Gitea. Use 1.17.0 or newer.', + `Gitea-native automerge: not supported on this version of ${defaults.isForgejo ? 'Forgejo' : 'Gitea'}. Use ${defaults.isForgejo ? '10.0.0' : '1.24.0'} or newer.`, ); } } @@ -592,7 +594,8 @@ const platform: Platform = { // would cause a HTTP 409 conflict error, which we hereby gracefully handle. if (err.statusCode === 409) { logger.warn( - `Attempting to gracefully recover from 409 Conflict response in createPr(${title}, ${sourceBranch})`, + { prTitle: title, sourceBranch }, + 'Attempting to gracefully recover from 409 Conflict response in createPr()', ); // Refresh cached PR list and search for pull request with matching information diff --git a/lib/modules/platform/gitea/pr-cache.ts b/lib/modules/platform/gitea/pr-cache.ts index e6f363500dd03e..82d935de312186 100644 --- a/lib/modules/platform/gitea/pr-cache.ts +++ b/lib/modules/platform/gitea/pr-cache.ts @@ -1,5 +1,7 @@ import { dequal } from 'dequal'; import { DateTime } from 'luxon'; +import { TEMPORARY_ERROR } from '../../../constants/error-messages'; +import { logger } from '../../../logger'; import * as memCache from '../../../util/cache/memory'; import { getCache } from '../../../util/cache/repository'; import type { GiteaHttp } from '../../../util/http/gitea'; @@ -83,7 +85,7 @@ export class GiteaPrCache { prCache.setPr(item); } - private reconcile(rawItems: PR[]): boolean { + private reconcile(rawItems: (PR | null)[]): boolean { const { items } = this.cache; let { updated_at } = this.cache; const cacheTime = updated_at ? DateTime.fromISO(updated_at) : null; @@ -91,6 +93,12 @@ export class GiteaPrCache { let needNextPage = true; for (const rawItem of rawItems) { + if (!rawItem) { + logger.warn('Gitea PR is empty, throwing temporary error'); + // Gitea API sometimes returns empty PRs, so we throw a temporary error + // https://github.com/go-gitea/gitea/blob/fcd096231ac2deaefbca10a7db1b9b01f1da93d7/services/convert/pull.go#L34-L52 + throw new Error(TEMPORARY_ERROR); + } const id = rawItem.number; const newItem = toRenovatePR(rawItem, this.author); @@ -127,10 +135,14 @@ export class GiteaPrCache { `${API_PATH}/repos/${this.repo}/pulls?${query}`; while (url) { - const res: HttpResponse = await http.getJson(url, { - memCache: false, - paginate: false, - }); + // TODO: use zod, typescript can't infer the type of the response #22198 + const res: HttpResponse<(PR | null)[]> = await http.getJsonUnchecked( + url, + { + memCache: false, + paginate: false, + }, + ); const needNextPage = this.reconcile(res.body); if (!needNextPage) { diff --git a/lib/modules/platform/gitea/readme.md b/lib/modules/platform/gitea/readme.md index 8159630fc61343..4e88c42739cc1a 100644 --- a/lib/modules/platform/gitea/readme.md +++ b/lib/modules/platform/gitea/readme.md @@ -31,7 +31,7 @@ If you use Gitea packages, add the `read:packages` scope. ## Unsupported platform features/concepts - **Adding reviewers to PRs not supported**: Gitea versions older than `v1.14.0` do not have the required API. -- **`platformAutomerge` (`true` by default) for platform-native automerge not supported**: Gitea versions older than v1.17.0 do not have the required API. +- **`platformAutomerge` (`true` by default) for platform-native automerge not supported**: Gitea versions older than v1.24.0 and Forgejo versions older than v10.0.0 don't support required branch autodelete for automerge. - **Git upload filters**: If you're using a Gitea version older than `v1.16.0` then you must enable [clone filters](https://docs.gitea.io/en-us/clone-filters/). ## Features awaiting implementation diff --git a/lib/modules/platform/gitea/types.ts b/lib/modules/platform/gitea/types.ts index cf4fb50b22645b..dd383c57de2df8 100644 --- a/lib/modules/platform/gitea/types.ts +++ b/lib/modules/platform/gitea/types.ts @@ -138,8 +138,8 @@ export interface CommitStatus { id: number; status: CommitStatusType; context: string; - description: string; - target_url: string; + description?: string; + target_url?: string; created_at: string; } @@ -199,6 +199,7 @@ export interface PRUpdateParams { export interface PRMergeParams { Do: PRMergeMethod; merge_when_checks_succeed?: boolean; + delete_branch_after_merge?: boolean; } export type CommentCreateParams = CommentUpdateParams; diff --git a/lib/modules/platform/github/index.ts b/lib/modules/platform/github/index.ts index 8551653ce0dac3..2ef5eb87dac3f3 100644 --- a/lib/modules/platform/github/index.ts +++ b/lib/modules/platform/github/index.ts @@ -251,7 +251,7 @@ export async function initPlatform({ async function fetchRepositories(): Promise { try { if (isGHApp()) { - const res = await githubApi.getJson<{ + const res = await githubApi.getJsonUnchecked<{ repositories: GhRestRepo[]; }>(`installation/repositories?per_page=100`, { paginationField: 'repositories', @@ -259,7 +259,7 @@ async function fetchRepositories(): Promise { }); return res.body.repositories; } else { - const res = await githubApi.getJson( + const res = await githubApi.getJsonUnchecked( `user/repos?per_page=100`, { paginate: 'all' }, ); @@ -313,7 +313,7 @@ async function getBranchProtection( if (config.parentRepo) { return {}; } - const res = await githubApi.getJson( + const res = await githubApi.getJsonUnchecked( `repos/${config.repository}/branches/${escapeHash(branchName)}/protection`, { cacheProvider: repoCacheProvider }, ); @@ -338,7 +338,10 @@ export async function getRawFile( if (branchOrTag) { url += `?ref=` + branchOrTag; } - const res = await githubApi.getJson<{ content: string }>(url, httpOptions); + const res = await githubApi.getJsonUnchecked<{ content: string }>( + url, + httpOptions, + ); const buf = res.body.content; const str = fromBase64(buf); return str; @@ -361,7 +364,7 @@ export async function listForks( // Get list of existing repos const url = `repos/${repository}/forks?per_page=100`; const repos = ( - await githubApi.getJson(url, { + await githubApi.getJsonUnchecked(url, { token, paginate: true, pageLimit: 100, @@ -792,7 +795,7 @@ function cachePr(pr?: GhPr | null): void { // Fetch fresh Pull Request and cache it when possible async function fetchPr(prNo: number): Promise { try { - const { body: ghRestPr } = await githubApi.getJson( + const { body: ghRestPr } = await githubApi.getJsonUnchecked( `repos/${config.parentRepo ?? config.repository}/pulls/${prNo}`, ); const result = coerceRestPr(ghRestPr); @@ -857,7 +860,7 @@ export async function findPr({ const repo = config.parentRepo ?? config.repository; const org = repo?.split('/')[0]; // PR might have been created by anyone, so don't use the cached Renovate PR list - const { body: prList } = await githubApi.getJson( + const { body: prList } = await githubApi.getJsonUnchecked( `repos/${repo}/pulls?head=${org}:${branchName}&state=open`, { cacheProvider: repoCacheProvider }, ); @@ -996,10 +999,11 @@ async function getStatus( const branch = escapeHash(branchName); const url = `repos/${config.repository}/commits/${branch}/status`; - const { body: status } = await githubApi.getJson(url, { - memCache: useCache, - cacheProvider: repoCacheProvider, - }); + const { body: status } = + await githubApi.getJsonUnchecked(url, { + memCache: useCache, + cacheProvider: repoCacheProvider, + }); return status; } @@ -1053,7 +1057,7 @@ export async function getBranchStatus( paginationField: 'check_runs', }; const checkRunsRaw = ( - await githubApi.getJson<{ + await githubApi.getJsonUnchecked<{ check_runs: { name: string; status: string; conclusion: string }[]; }>(checkRunsUrl, opts) ).body; @@ -1116,7 +1120,9 @@ async function getStatusCheck( const url = `repos/${config.repository}/commits/${branchCommit}/statuses`; return ( - await githubApi.getJson(url, { memCache: useCache }) + await githubApi.getJsonUnchecked(url, { + memCache: useCache, + }) ).body; } @@ -1547,10 +1553,13 @@ async function getComments(issueNo: number): Promise { const repo = config.parentRepo ?? config.repository; const url = `repos/${repo}/issues/${issueNo}/comments?per_page=100`; try { - const { body: comments } = await githubApi.getJson(url, { - paginate: true, - cacheProvider: repoCacheProvider, - }); + const { body: comments } = await githubApi.getJsonUnchecked( + url, + { + paginate: true, + cacheProvider: repoCacheProvider, + }, + ); logger.debug(`Found ${comments.length} comments`); return comments; } catch (err) /* istanbul ignore next */ { diff --git a/lib/modules/platform/github/pr.ts b/lib/modules/platform/github/pr.ts index 66b37aa3426fcc..5e32084ca23592 100644 --- a/lib/modules/platform/github/pr.ts +++ b/lib/modules/platform/github/pr.ts @@ -75,7 +75,7 @@ export async function getPrCache( const perPage = isInitial ? 100 : 20; const urlPath = `repos/${repo}/pulls?per_page=${perPage}&state=all&sort=updated&direction=desc&page=${pageIdx}`; - const res = await http.getJson(urlPath, opts); + const res = await http.getJsonUnchecked(urlPath, opts); apiQuotaAffected = true; requestsTotal += 1; diff --git a/lib/modules/platform/github/user.ts b/lib/modules/platform/github/user.ts index 5fabaedb94d662..c2fced06c02866 100644 --- a/lib/modules/platform/github/user.ts +++ b/lib/modules/platform/github/user.ts @@ -32,12 +32,13 @@ export async function getUserDetails( ): Promise { try { const userData = ( - await githubApi.getJson<{ login: string; name: string; id: number }>( - endpoint + 'user', - { - token, - }, - ) + await githubApi.getJsonUnchecked<{ + login: string; + name: string; + id: number; + }>(endpoint + 'user', { + token, + }) ).body; return { username: userData.login, @@ -56,9 +57,12 @@ export async function getUserEmail( ): Promise { try { const emails = ( - await githubApi.getJson<{ email: string }[]>(endpoint + 'user/emails', { - token, - }) + await githubApi.getJsonUnchecked<{ email: string }[]>( + endpoint + 'user/emails', + { + token, + }, + ) ).body; return emails?.[0].email ?? null; } catch { diff --git a/lib/modules/platform/gitlab/http.ts b/lib/modules/platform/gitlab/http.ts index 51f6fc401f56a5..deb011fc5828cb 100644 --- a/lib/modules/platform/gitlab/http.ts +++ b/lib/modules/platform/gitlab/http.ts @@ -7,7 +7,9 @@ export const gitlabApi = new GitlabHttp(); export async function getUserID(username: string): Promise { const userInfo = ( - await gitlabApi.getJson<{ id: number }[]>(`users?username=${username}`) + await gitlabApi.getJsonUnchecked<{ id: number }[]>( + `users?username=${username}`, + ) ).body; if (is.emptyArray(userInfo)) { @@ -22,7 +24,9 @@ export async function getUserID(username: string): Promise { async function getMembers(group: string): Promise { const groupEncoded = encodeURIComponent(group); return ( - await gitlabApi.getJson(`groups/${groupEncoded}/members`) + await gitlabApi.getJsonUnchecked( + `groups/${groupEncoded}/members`, + ) ).body; } @@ -39,7 +43,8 @@ export async function getMemberUsernames(group: string): Promise { export async function isUserBusy(user: string): Promise { try { const url = `/users/${user}/status`; - const userStatus = (await gitlabApi.getJson(url)).body; + const userStatus = (await gitlabApi.getJsonUnchecked(url)) + .body; return userStatus.availability === 'busy'; } catch (err) { logger.warn({ err }, 'Failed to get user status'); diff --git a/lib/modules/platform/gitlab/index.spec.ts b/lib/modules/platform/gitlab/index.spec.ts index 6eb62b886f7c40..6d1176dbcbbc4e 100644 --- a/lib/modules/platform/gitlab/index.spec.ts +++ b/lib/modules/platform/gitlab/index.spec.ts @@ -51,6 +51,7 @@ describe('modules/platform/gitlab/index', () => { token: '123test', }); delete process.env.GITLAB_IGNORE_REPO_URL; + delete process.env.RENOVATE_X_GITLAB_BRANCH_STATUS_CHECK_ATTEMPTS; delete process.env.RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY; delete process.env.RENOVATE_X_GITLAB_AUTO_MERGEABLE_CHECK_ATTEMPS; delete process.env.RENOVATE_X_GITLAB_MERGE_REQUEST_DELAY; @@ -1058,6 +1059,51 @@ describe('modules/platform/gitlab/index', () => { describe('setBranchStatus', () => { const states: BranchStatus[] = ['green', 'yellow', 'red']; + it('should log message that branch commit SHA not found', async () => { + git.getBranchCommit.mockReturnValue(null); + await gitlab.setBranchStatus({ + branchName: 'some-branch', + context: 'some-context', + description: 'some-description', + state: 'green', + url: 'some-url', + }); + expect(logger.warn).toHaveBeenCalledWith( + 'Failed to get the branch commit SHA', + ); + }); + + it('should log message that failed to retrieve commit pipeline', async () => { + const scope = await initRepo(); + scope + .post( + '/api/v4/projects/some%2Frepo/statuses/0d9c7726c3d628b7e28af234595cfd20febdbf8e', + ) + .reply(200, {}) + .get( + '/api/v4/projects/some%2Frepo/repository/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e/statuses', + ) + .reply(200, []) + .get( + '/api/v4/projects/some%2Frepo/repository/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e', + ) + .reply(200, {}); + + timers.setTimeout.mockImplementation(() => { + throw new Error(); + }); + await gitlab.setBranchStatus({ + branchName: 'some-branch', + context: 'some-context', + description: 'some-description', + state: 'green', + url: 'some-url', + }); + expect(logger.warn).toHaveBeenCalledWith( + 'Failed to retrieve commit pipeline', + ); + }); + it.each(states)('sets branch status %s', async (state) => { const scope = await initRepo(); scope @@ -1072,7 +1118,8 @@ describe('modules/platform/gitlab/index', () => { .get( '/api/v4/projects/some%2Frepo/repository/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e', ) - .reply(200, []); + .times(3) + .reply(200, {}); await expect( gitlab.setBranchStatus({ @@ -1099,7 +1146,8 @@ describe('modules/platform/gitlab/index', () => { .get( '/api/v4/projects/some%2Frepo/repository/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e', ) - .reply(200, []); + .times(3) + .reply(200, {}); await gitlab.setBranchStatus({ branchName: 'some-branch', @@ -1109,7 +1157,7 @@ describe('modules/platform/gitlab/index', () => { url: 'some-url', }); - expect(timers.setTimeout.mock.calls).toHaveLength(1); + expect(timers.setTimeout.mock.calls).toHaveLength(3); expect(timers.setTimeout.mock.calls[0][0]).toBe(1000); }); @@ -1131,6 +1179,10 @@ describe('modules/platform/gitlab/index', () => { .get( '/api/v4/projects/some%2Frepo/repository/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e', ) + .reply(200, {}) + .get( + '/api/v4/projects/some%2Frepo/repository/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e', + ) .reply(200, { last_pipeline: { id: 123 } }); await expect( @@ -1146,6 +1198,7 @@ describe('modules/platform/gitlab/index', () => { it('waits for RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY ms when set', async () => { const delay = 5000; + const retry = 2; process.env.RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY = String(delay); const scope = await initRepo(); @@ -1161,7 +1214,50 @@ describe('modules/platform/gitlab/index', () => { .get( '/api/v4/projects/some%2Frepo/repository/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e', ) - .reply(200, []); + .times(3) + .reply(200, {}); + + await gitlab.setBranchStatus({ + branchName: 'some-branch', + context: 'some-context', + description: 'some-description', + state: 'green', + url: 'some-url', + }); + + expect(timers.setTimeout.mock.calls).toHaveLength(retry + 1); + expect(timers.setTimeout.mock.calls[0][0]).toBe(delay); + expect(logger.debug).toHaveBeenCalledWith( + `Pipeline not yet created. Retrying 1`, + ); + expect(logger.debug).toHaveBeenCalledWith( + `Pipeline not yet created. Retrying 2`, + ); + expect(logger.debug).toHaveBeenCalledWith( + `Pipeline not yet created after 3 attempts`, + ); + }); + + it('do RENOVATE_X_GITLAB_BRANCH_STATUS_CHECK_ATTEMPTS attemps when set', async () => { + const delay = 1000; + const retry = 5; + process.env.RENOVATE_X_GITLAB_BRANCH_STATUS_CHECK_ATTEMPTS = `${retry}`; + + const scope = await initRepo(); + scope + .post( + '/api/v4/projects/some%2Frepo/statuses/0d9c7726c3d628b7e28af234595cfd20febdbf8e', + ) + .reply(200, {}) + .get( + '/api/v4/projects/some%2Frepo/repository/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e/statuses', + ) + .reply(200, []) + .get( + '/api/v4/projects/some%2Frepo/repository/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e', + ) + .times(retry + 1) + .reply(200, {}); await gitlab.setBranchStatus({ branchName: 'some-branch', @@ -1171,7 +1267,7 @@ describe('modules/platform/gitlab/index', () => { url: 'some-url', }); - expect(timers.setTimeout.mock.calls).toHaveLength(1); + expect(timers.setTimeout.mock.calls).toHaveLength(retry + 1); expect(timers.setTimeout.mock.calls[0][0]).toBe(delay); }); }); diff --git a/lib/modules/platform/gitlab/index.ts b/lib/modules/platform/gitlab/index.ts index 1010f448cdc7f1..20e5b1bb9ba134 100644 --- a/lib/modules/platform/gitlab/index.ts +++ b/lib/modules/platform/gitlab/index.ts @@ -117,7 +117,7 @@ export async function initPlatform({ try { if (!gitAuthor) { const user = ( - await gitlabApi.getJson<{ + await gitlabApi.getJsonUnchecked<{ email: string; name: string; id: number; @@ -133,7 +133,9 @@ export async function initPlatform({ gitlabVersion = process.env.RENOVATE_X_PLATFORM_VERSION; } else { const version = ( - await gitlabApi.getJson<{ version: string }>('version', { token }) + await gitlabApi.getJsonUnchecked<{ version: string }>('version', { + token, + }) ).body; gitlabVersion = version.version; } @@ -191,7 +193,7 @@ export async function getRepos(config?: AutodiscoverConfig): Promise { await pMap( urls, (url) => - gitlabApi.getJson(url, { + gitlabApi.getJsonUnchecked(url, { paginate: true, }), { @@ -226,7 +228,7 @@ export async function getRawFile( const url = `projects/${repo}/repository/files/${escapedFileName}?ref=` + (branchOrTag ?? `HEAD`); - const res = await gitlabApi.getJson<{ content: string }>(url); + const res = await gitlabApi.getJsonUnchecked<{ content: string }>(url); const buf = res.body.content; const str = Buffer.from(buf, 'base64').toString(); return str; @@ -314,7 +316,7 @@ export async function initRepo({ let res: HttpResponse; try { - res = await gitlabApi.getJson( + res = await gitlabApi.getJsonUnchecked( `projects/${config.repository}`, ); if (res.body.archived) { @@ -433,7 +435,7 @@ async function getStatus( }/repository/commits/${branchSha!}/statuses`; return ( - await gitlabApi.getJson(url, { + await gitlabApi.getJsonUnchecked(url, { paginate: true, memCache: useCache, }) @@ -548,9 +550,12 @@ async function fetchPrList(): Promise { const query = getQueryString(searchParams); const urlString = `projects/${config.repository}/merge_requests?${query}`; try { - const res = await gitlabApi.getJson(urlString, { - paginate: true, - }); + const res = await gitlabApi.getJsonUnchecked( + urlString, + { + paginate: true, + }, + ); return res.body.map((pr) => prInfo(pr)); } catch (err) /* istanbul ignore next */ { logger.debug({ err }, 'Error fetching PR list'); @@ -571,7 +576,7 @@ export async function getPrList(): Promise { async function ignoreApprovals(pr: number): Promise { try { const url = `projects/${config.repository}/merge_requests/${pr}/approval_rules`; - const { body: rules } = await gitlabApi.getJson< + const { body: rules } = await gitlabApi.getJsonUnchecked< { name: string; rule_type: string; @@ -655,7 +660,7 @@ async function tryPrAutomerge( // Check for correct merge request status before setting `merge_when_pipeline_succeeds` to `true`. for (let attempt = 1; attempt <= retryTimes; attempt += 1) { - const { body } = await gitlabApi.getJson<{ + const { body } = await gitlabApi.getJsonUnchecked<{ merge_status: string; detailed_merge_status?: string; pipeline: { @@ -929,7 +934,7 @@ export async function findPr({ if (includeOtherAuthors) { // PR might have been created by anyone, so don't use the cached Renovate MR list - const response = await gitlabApi.getJson( + const response = await gitlabApi.getJsonUnchecked( `projects/${config.repository}/merge_requests?source_branch=${branchName}&state=opened`, ); @@ -989,9 +994,12 @@ export async function setBranchStatus({ }: BranchStatusConfig): Promise { // First, get the branch commit SHA const branchSha = git.getBranchCommit(branchName); + if (!branchSha) { + logger.warn('Failed to get the branch commit SHA'); + return; + } // Now, check the statuses for that commit - // TODO: types (#22198) - const url = `projects/${config.repository}/statuses/${branchSha!}`; + const url = `projects/${config.repository}/statuses/${branchSha}`; let state = 'success'; if (renovateState === 'yellow') { state = 'pending'; @@ -1008,21 +1016,38 @@ export async function setBranchStatus({ options.target_url = targetUrl; } - if (branchSha) { - const commitUrl = `projects/${config.repository}/repository/commits/${branchSha}`; - await gitlabApi - .getJsonSafe(commitUrl, LastPipelineId) - .onValue((pipelineId) => { - options.pipeline_id = pipelineId; - }); - } + const retryTimes = parseInteger( + process.env.RENOVATE_X_GITLAB_BRANCH_STATUS_CHECK_ATTEMPTS, + 2, + ); try { - // give gitlab some time to create pipelines for the sha - await setTimeout( - parseInteger(process.env.RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY, 1000), - ); + for (let attempt = 1; attempt <= retryTimes + 1; attempt += 1) { + const commitUrl = `projects/${config.repository}/repository/commits/${branchSha}`; + await gitlabApi + .getJsonSafe(commitUrl, { memCache: false }, LastPipelineId) + .onValue((pipelineId) => { + options.pipeline_id = pipelineId; + }); + if (options.pipeline_id !== undefined) { + break; + } + if (attempt >= retryTimes + 1) { + logger.debug(`Pipeline not yet created after ${attempt} attempts`); + } else { + logger.debug(`Pipeline not yet created. Retrying ${attempt}`); + } + // give gitlab some time to create pipelines for the sha + await setTimeout( + parseInteger(process.env.RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY, 1000), + ); + } + } catch (err) { + logger.debug({ err }); + logger.warn('Failed to retrieve commit pipeline'); + } + try { await gitlabApi.postJson(url, { body: options }); // update status cache @@ -1054,7 +1079,7 @@ export async function getIssueList(): Promise { searchParams.scope = 'created_by_me'; } const query = getQueryString(searchParams); - const res = await gitlabApi.getJson< + const res = await gitlabApi.getJsonUnchecked< { iid: number; title: string; labels: string[] }[] >(`projects/${config.repository}/issues?${query}`, { memCache: false, @@ -1080,7 +1105,7 @@ export async function getIssue( ): Promise { try { const issueBody = ( - await gitlabApi.getJson<{ description: string }>( + await gitlabApi.getJsonUnchecked<{ description: string }>( `projects/${config.repository}/issues/${number}`, { memCache: useCache }, ) @@ -1127,7 +1152,7 @@ export async function ensureIssue({ } if (issue) { const existingDescription = ( - await gitlabApi.getJson<{ description: string }>( + await gitlabApi.getJsonUnchecked<{ description: string }>( `projects/${config.repository}/issues/${issue.iid}`, ) ).body.description; @@ -1302,7 +1327,7 @@ async function getComments(issueNo: number): Promise { logger.debug(`Getting comments for #${issueNo}`); const url = `projects/${config.repository}/merge_requests/${issueNo}/notes`; const comments = ( - await gitlabApi.getJson(url, { paginate: true }) + await gitlabApi.getJsonUnchecked(url, { paginate: true }) ).body; logger.debug(`Found ${comments.length} comments`); return comments; diff --git a/lib/modules/platform/gitlab/merge-request.ts b/lib/modules/platform/gitlab/merge-request.ts index 91eb6bf88d1a5d..0a657fa95c970f 100644 --- a/lib/modules/platform/gitlab/merge-request.ts +++ b/lib/modules/platform/gitlab/merge-request.ts @@ -9,7 +9,7 @@ export async function getMR( logger.debug(`getMR(${iid})`); const url = `projects/${repository}/merge_requests/${iid}?include_diverged_commits_count=1`; - return (await gitlabApi.getJson(url)).body; + return (await gitlabApi.getJsonUnchecked(url)).body; } export async function updateMR( diff --git a/lib/modules/versioning/api.ts b/lib/modules/versioning/api.ts index 454d0f631ade1a..10bae3b202c4cb 100644 --- a/lib/modules/versioning/api.ts +++ b/lib/modules/versioning/api.ts @@ -1,3 +1,4 @@ +import * as awsEksAddon from './aws-eks-addon'; import * as amazonMachineImage from './aws-machine-image'; import * as azureRestApi from './azure-rest-api'; import * as bazelModule from './bazel-module'; @@ -45,6 +46,7 @@ import * as unity3d from './unity3d'; const api = new Map(); export default api; +api.set(awsEksAddon.id, awsEksAddon.api); api.set(amazonMachineImage.id, amazonMachineImage.api); api.set(azureRestApi.id, azureRestApi.api); api.set(bazelModule.id, bazelModule.api); diff --git a/lib/modules/versioning/aws-eks-addon/index.spec.ts b/lib/modules/versioning/aws-eks-addon/index.spec.ts new file mode 100644 index 00000000000000..f7c559ae87da0f --- /dev/null +++ b/lib/modules/versioning/aws-eks-addon/index.spec.ts @@ -0,0 +1,146 @@ +import aws from '.'; + +describe('modules/versioning/aws-eks-addon/index', () => { + describe('parse(version)', () => { + it('should return 1.23.7 and release version', () => { + expect(aws.getMajor('v1.20.7-eksbuild.1')).toBe(1); + expect(aws.getMinor('v1.23.7-eksbuild.1')).toBe(23); + expect(aws.getPatch('v1.20.7-eksbuild.1')).toBe(7); + }); + }); + + describe('isValid(version)', () => { + it.each` + input | expected + ${''} | ${false} + ${'.1..'} | ${false} + ${'abrakadabra'} | ${false} + ${'v1'} | ${false} + ${'v1.'} | ${false} + ${'v1...-eksbuild.1'} | ${false} + ${'v1-eksbuild.1'} | ${false} + ${'v1.a-eksbuild.1'} | ${false} + ${'v1.23-eksbuild.1'} | ${false} + ${'1.23.1-eksbuild.a'} | ${false} + ${'v1.11.7'} | ${false} + ${'v1.11.7.6'} | ${false} + ${'v1.11.7-noneksbuild'} | ${false} + ${'v1.11.7-noneksbuild.1'} | ${false} + ${'v1.11.7-eksbuild'} | ${false} + ${'v1.11.7.3-eksbuild.1'} | ${false} + ${'v1.23.1-eksbuild.1'} | ${true} + ${'1.23.1-eksbuild.1'} | ${true} + ${'v1.23.1-eksbuild.11'} | ${true} + `('isValid("$input") === $expected', ({ input, expected }) => { + const actual = aws.isValid(input); + expect(actual).toBe(expected); + }); + }); + + describe('isVersion(version)', () => { + it.each` + input | expected + ${''} | ${false} + ${'abrakadabra'} | ${false} + ${'v1'} | ${false} + ${'v1.'} | ${false} + ${'v1-eksbuild.1'} | ${false} + ${'v1.a-eksbuild.1'} | ${false} + ${'v1.23-eksbuild.1'} | ${false} + ${'1.23.1-eksbuild.a'} | ${false} + ${'v1.11.7'} | ${false} + ${'v1.11.7.6'} | ${false} + ${'v1.11.7-noneksbuild'} | ${false} + ${'v1.11.7-noneksbuild.1'} | ${false} + ${'v1.11.7-eksbuild'} | ${false} + ${'v1.11.7.3-eksbuild.1'} | ${false} + ${'v1.23.1-eksbuild.1'} | ${true} + ${'1.23.1-eksbuild.1'} | ${true} + ${'v1.23.1-eksbuild.11'} | ${true} + `('isValid("$input") === $expected', ({ input, expected }) => { + const actual = aws.isVersion(input); + expect(actual).toBe(expected); + }); + }); + + describe('isCompatible(version)', () => { + it.each` + input | expected + ${''} | ${false} + ${'abrakadabra'} | ${false} + ${'v1'} | ${false} + ${'v1.'} | ${false} + ${'v1-eksbuild.1'} | ${false} + ${'v1.a-eksbuild.1'} | ${false} + ${'v1.23-eksbuild.1'} | ${false} + ${'1.23.1-eksbuild.1'} | ${false} + ${'1.23.1-eksbuild.a'} | ${false} + ${'v1.11.7'} | ${false} + ${'v1.11.7.6'} | ${false} + ${'v1.11.7-noneksbuild'} | ${false} + ${'v1.11.7-noneksbuild.1'} | ${false} + ${'v1.11.7-eksbuild'} | ${false} + ${'v1.11.7.3-eksbuild.1'} | ${false} + `('isCompatible("$input") === $expected', ({ input, expected }) => { + const actual = aws.isCompatible(input); + expect(actual).toBe(expected); + }); + }); + + describe('isCompatible(version,range)', () => { + it.each` + version | current | expected + ${'1.23.1-eksbuild.1'} | ${'1.23.1-eksbuild.2'} | ${true} + ${'v1.23.1-eksbuild.1'} | ${'1.23.1-eksbuild.2'} | ${true} + ${'v1.23.1-eksbuild.1'} | ${'1.23.1-eksbuild.21'} | ${true} + ${'v1.11.7-eksbuild.1'} | ${'v1.11.7-noneksbuild.1'} | ${false} + ${'v1.11.7'} | ${'v1.11.7-noneksbuild.1'} | ${false} + ${'v1-eksbuild.1'} | ${'artful'} | ${false} + ${'v1.11.7.1-eksbuild.1'} | ${'v1.11.7-eksbuild.1'} | ${false} + `( + 'isCompatible($version, $current) === $expected', + ({ version, current, expected }) => { + const actual = aws.isCompatible(version, current); + expect(actual).toBe(expected); + }, + ); + }); + + describe('isGreaterThan(version1, version2)', () => { + it.each` + version | other | expected + ${'v1.11.7-eksbuild.1'} | ${'v1.11.7-eksbuild.0'} | ${true} + ${'v1.11.7-eksbuild.11'} | ${'v1.11.7-eksbuild.1'} | ${true} + ${'v1.22.7-eksbuild.2'} | ${'v1.20.7-eksbuild.1'} | ${true} + ${'v1.22.7-eksbuild.2'} | ${'v1.22.7'} | ${true} + ${'v1.20.7-eksbuild.1'} | ${'v2.0.0'} | ${true} + ${'v1.20.7-eksbuild.1'} | ${'v1.20.7-eksbuild.2'} | ${false} + ${'v1.20.6-eksbuild.1'} | ${'v1.20.7-eksbuild.2'} | ${false} + ${'v1.20.7-eksbuild.1'} | ${'v2.0.0-eksbuild.1'} | ${false} + `( + 'isGreaterThan($version, $other) === $expected', + ({ version, other, expected }) => { + const actual = aws.isGreaterThan(version, other); + expect(actual).toBe(expected); + }, + ); + }); + + it('getSatisfyingVersion', () => { + expect( + aws.getSatisfyingVersion(['v1.20.7-eksbuild.1'], 'v1.20.7-eksbuild.1'), + ).toBe('v1.20.7-eksbuild.1'); + expect( + aws.getSatisfyingVersion( + ['v1.20.7-eksbuild.1', 'v1.20.7-eksbuild.2', 'v1.20.7-eksbuild.7'], + 'v1.20.7-eksbuild.3', + ), + ).toBeNull(); + expect( + aws.getSatisfyingVersion( + ['v1.20.7-eksbuild.1', 'v1.20.7-eksbuild.2'], + 'v1.20.7-eksbuild.3', + ), + ).toBeNull(); + }); +}); diff --git a/lib/modules/versioning/aws-eks-addon/index.ts b/lib/modules/versioning/aws-eks-addon/index.ts new file mode 100644 index 00000000000000..c004bb1dc66ff6 --- /dev/null +++ b/lib/modules/versioning/aws-eks-addon/index.ts @@ -0,0 +1,20 @@ +import { RegExpVersioningApi } from '../regex'; +import type { VersioningApi } from '../types'; + +export const id = 'aws-eks-addon'; +export const displayName = 'aws-eks-addon'; +export const urls = []; +export const supportsRanges = false; + +export class AwsEKSAddonVersioningApi extends RegExpVersioningApi { + static versionRegex = + '^v?(?\\d+)\\.(?\\d+)\\.(?\\d+)(?-eksbuild\\.)(?\\d+)$'; + + public constructor() { + super(AwsEKSAddonVersioningApi.versionRegex); + } +} + +export const api: VersioningApi = new AwsEKSAddonVersioningApi(); + +export default api; diff --git a/lib/modules/versioning/aws-eks-addon/readme.md b/lib/modules/versioning/aws-eks-addon/readme.md new file mode 100644 index 00000000000000..4b1fbc6fdaae6a --- /dev/null +++ b/lib/modules/versioning/aws-eks-addon/readme.md @@ -0,0 +1,18 @@ +AWS versioning syntax is used for EKS Addon updates. + +It is based off [Semantic Versioning 2.0](https://semver.org) but with a subset of addon `build metadata` syntax. + +At the moment every ESK Addon that matches the regex `^v?\d+\.\d+\.\d+-eksbuild\.\d+$` is considered a valid "release". + +**Key Points about EKS Addon Versioning** + +1. Versioning Scheme: Add-ons typically follow a semantic versioning scheme (e.g., Major.Minor.Patch). This helps in understanding the significance of changes between versions: + + - `Major`: Indicates significant changes or breaking API changes for plugin version. + - `Minor`: Introduces new features or enhancements for plugin version. + - `Patch`: Includes bug fixes and minor improvements for plugin version. + - `Build Metadata` : It helps differentiate this particular release from others that might have been built independently. + +2. Default Versions: When creating a new EKS cluster, AWS often selects a default version for each addon based on the cluster's Kubernetes version and other factors. This default version is usually the most stable and recommended for the specific cluster configuration + +3. Build metadata. Example `eksbuild.1`. The `eksbuild.1` part signifies a specific build or release within the `1.19.0` version, likely managed by the EKS build system. It helps differentiate this particular release from others that might have been built independently. The build metadata provides additional context about the specific release, which can be useful for tracking and troubleshooting. diff --git a/lib/modules/versioning/cargo/index.spec.ts b/lib/modules/versioning/cargo/index.spec.ts index 8198b12f405592..0c5e6a5ae52a5f 100644 --- a/lib/modules/versioning/cargo/index.spec.ts +++ b/lib/modules/versioning/cargo/index.spec.ts @@ -132,6 +132,7 @@ describe('modules/versioning/cargo/index', () => { ${'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'} + ${'1.2'} | ${'replace'} | ${'1.2.3'} | ${'1.3.0'} | ${'1.2'} ${'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 c78fc095cecfcb..e8309f06a6826c 100644 --- a/lib/modules/versioning/cargo/index.ts +++ b/lib/modules/versioning/cargo/index.ts @@ -106,6 +106,9 @@ function getNewValue({ res += newVersion; return res; } + if (rangeStrategy === 'replace' && matches(newVersion, currentValue)) { + return currentValue; + } const newSemver = npm.getNewValue({ currentValue: cargo2npm(currentValue), rangeStrategy, @@ -142,6 +145,7 @@ function getNewValue({ const components = currentValue.split('.').length; newCargo = withoutCaret.split('.').slice(0, components).join('.'); } + return newCargo; } diff --git a/lib/modules/versioning/gradle/compare.ts b/lib/modules/versioning/gradle/compare.ts index 2497527108363a..922ba50ff858ea 100644 --- a/lib/modules/versioning/gradle/compare.ts +++ b/lib/modules/versioning/gradle/compare.ts @@ -49,10 +49,6 @@ export function tokenize(versionStr: string): Token[] | null { let currentVal = ''; function yieldToken(): void { - if (currentVal === '') { - // We tried to yield an empty token, which means we're in a bad state. - result = null; - } if (result) { const val = currentVal; if (regEx(/^\d+$/).test(val)) { @@ -73,8 +69,12 @@ export function tokenize(versionStr: string): Token[] | null { if (nextChar === null) { yieldToken(); } else if (isSeparator(nextChar)) { - yieldToken(); - currentVal = ''; + if (prevChar && !isSeparator(prevChar)) { + yieldToken(); + currentVal = ''; + } else { + result = null; + } } else if (prevChar !== null && isTransition(prevChar, nextChar)) { yieldToken(); currentVal = nextChar; @@ -243,11 +243,13 @@ export function parsePrefixRange(input: string): PrefixRange | null { return { tokens: [] }; } - const postfixRegex = regEx(/[-._]\+$/); + const postfixRegex = regEx(/[^-._+][-._]\+$/); if (postfixRegex.test(input)) { const prefixValue = input.replace(regEx(/[-._]\+$/), ''); const tokens = tokenize(prefixValue); - return tokens ? { tokens } : null; + if (tokens) { + return { tokens }; + } } return null; @@ -306,12 +308,37 @@ export function parseMavenBasedRange(input: string): MavenBasedRange | null { return null; } +interface SingleVersionRange { + val: string; +} + +const singleVersionRangeRegex = regEx(/^\[\s*(?[-._+a-zA-Z0-9]*?)\s*\]$/); + +export function parseSingleVersionRange( + input: string, +): SingleVersionRange | null { + const matchGroups = singleVersionRangeRegex.exec(input)?.groups; + if (!matchGroups) { + return null; + } + + const { val } = matchGroups; + if (!isVersion(val)) { + return null; + } + + return { val }; +} + export function isValid(str: string): boolean { if (!str) { return false; } return ( - isVersion(str) || !!parsePrefixRange(str) || !!parseMavenBasedRange(str) + isVersion(str) || + !!parsePrefixRange(str) || + !!parseMavenBasedRange(str) || + !!parseSingleVersionRange(str) ); } diff --git a/lib/modules/versioning/gradle/index.spec.ts b/lib/modules/versioning/gradle/index.spec.ts index ce635ea3eb6b0b..bd42d7fbf1a32c 100644 --- a/lib/modules/versioning/gradle/index.spec.ts +++ b/lib/modules/versioning/gradle/index.spec.ts @@ -2,307 +2,351 @@ import { compare, parseMavenBasedRange, parsePrefixRange } from './compare'; import { api } from '.'; describe('modules/versioning/gradle/index', () => { - it.each` - a | b | expected - ${'1'} | ${'1'} | ${0} - ${'a'} | ${'a'} | ${0} - ${'1a1'} | ${'1.a.1'} | ${0} - ${'1a1'} | ${'1-a-1'} | ${0} - ${'1a1'} | ${'1_a_1'} | ${0} - ${'1a1'} | ${'1+a+1'} | ${0} - ${'1.a.1'} | ${'1a1'} | ${0} - ${'1-a-1'} | ${'1a1'} | ${0} - ${'1_a_1'} | ${'1a1'} | ${0} - ${'1+a+1'} | ${'1a1'} | ${0} - ${'1.a.1'} | ${'1-a+1'} | ${0} - ${'1-a+1'} | ${'1.a-1'} | ${0} - ${'1.a-1'} | ${'1a1'} | ${0} - ${'dev'} | ${'dev'} | ${0} - ${'rc'} | ${'rc'} | ${0} - ${'preview'} | ${'preview'} | ${0} - ${'release'} | ${'release'} | ${0} - ${'final'} | ${'final'} | ${0} - ${'snapshot'} | ${'SNAPSHOT'} | ${0} - ${'SNAPSHOT'} | ${'snapshot'} | ${0} - ${'Hoxton.SR1'} | ${'Hoxton.sr-1'} | ${0} - ${'1.1'} | ${'1.2'} | ${-1} - ${'1.a'} | ${'1.1'} | ${-1} - ${'1.A'} | ${'1.B'} | ${-1} - ${'1.B'} | ${'1.a'} | ${-1} - ${'1.a'} | ${'1.b'} | ${-1} - ${'1.1'} | ${'1.1.0'} | ${-1} - ${'1.1.a'} | ${'1.1'} | ${-1} - ${'1.0-dev'} | ${'1.0-alpha'} | ${-1} - ${'1.0-alpha'} | ${'1.0-rc'} | ${-1} - ${'1.0-zeta'} | ${'1.0-rc'} | ${-1} - ${'1.0-rc'} | ${'1.0-final'} | ${-1} - ${'1.0-final'} | ${'1.0-ga'} | ${-1} - ${'1.0-ga'} | ${'1.0-release'} | ${-1} - ${'1.0-rc'} | ${'1.0-release'} | ${-1} - ${'1.0-final'} | ${'1.0'} | ${-1} - ${'1.0-alpha'} | ${'1.0-SNAPSHOT'} | ${-1} - ${'1.0-zeta'} | ${'1.0-SNAPSHOT'} | ${-1} - ${'1.0-zeta'} | ${'1.0-rc'} | ${-1} - ${'1.0-rc'} | ${'1.0'} | ${-1} - ${'1.0-preview'} | ${'1.0'} | ${-1} - ${'1.0'} | ${'1.0-20150201.121010-123'} | ${-1} - ${'1.0-20150201.121010-123'} | ${'1.1'} | ${-1} - ${'Hoxton.RELEASE'} | ${'Hoxton.SR1'} | ${-1} - ${'1.0-release'} | ${'1.0-sp-1'} | ${-1} - ${'1.0-sp-1'} | ${'1.0-sp-2'} | ${-1} - ${'1.2'} | ${'1.1'} | ${1} - ${'1.1'} | ${'1.1.a'} | ${1} - ${'1.B'} | ${'1.A'} | ${1} - ${'1.a'} | ${'1.B'} | ${1} - ${'1.b'} | ${'1.a'} | ${1} - ${'1.1.0'} | ${'1.1'} | ${1} - ${'1.1'} | ${'1.a'} | ${1} - ${'1.0-alpha'} | ${'1.0-dev'} | ${1} - ${'1.0-rc'} | ${'1.0-alpha'} | ${1} - ${'1.0-rc'} | ${'1.0-zeta'} | ${1} - ${'1.0-release'} | ${'1.0-rc'} | ${1} - ${'1.0-final'} | ${'1.0-rc'} | ${1} - ${'1.0-ga'} | ${'1.0-final'} | ${1} - ${'1.0-release'} | ${'1.0-ga'} | ${1} - ${'1.0-release'} | ${'1.0-final'} | ${1} - ${'1.0'} | ${'1.0-final'} | ${1} - ${'1.0-SNAPSHOT'} | ${'1.0-alpha'} | ${1} - ${'1.0-SNAPSHOT'} | ${'1.0-zeta'} | ${1} - ${'1.0-rc'} | ${'1.0-zeta'} | ${1} - ${'1.0'} | ${'1.0-rc'} | ${1} - ${'1.0'} | ${'1.0-preview'} | ${1} - ${'1.0-20150201.121010-123'} | ${'1.0'} | ${1} - ${'1.1'} | ${'1.0-20150201.121010-123'} | ${1} - ${'Hoxton.SR1'} | ${'Hoxton.RELEASE'} | ${1} - ${'1.0-sp-1'} | ${'1.0-release'} | ${1} - ${'1.0-sp-2'} | ${'1.0-sp-1'} | ${1} - ${''} | ${''} | ${0} - `('compare("$a", "$b") === $expected', ({ a, b, expected }) => { - expect(compare(a, b)).toEqual(expected); + describe('compare', () => { + it.each` + a | b | expected + ${'1'} | ${'1'} | ${0} + ${'a'} | ${'a'} | ${0} + ${'1a1'} | ${'1.a.1'} | ${0} + ${'1a1'} | ${'1-a-1'} | ${0} + ${'1a1'} | ${'1_a_1'} | ${0} + ${'1a1'} | ${'1+a+1'} | ${0} + ${'1.a.1'} | ${'1a1'} | ${0} + ${'1-a-1'} | ${'1a1'} | ${0} + ${'1_a_1'} | ${'1a1'} | ${0} + ${'1+a+1'} | ${'1a1'} | ${0} + ${'1.a.1'} | ${'1-a+1'} | ${0} + ${'1-a+1'} | ${'1.a-1'} | ${0} + ${'1.a-1'} | ${'1a1'} | ${0} + ${'dev'} | ${'dev'} | ${0} + ${'rc'} | ${'rc'} | ${0} + ${'preview'} | ${'preview'} | ${0} + ${'release'} | ${'release'} | ${0} + ${'final'} | ${'final'} | ${0} + ${'snapshot'} | ${'SNAPSHOT'} | ${0} + ${'SNAPSHOT'} | ${'snapshot'} | ${0} + ${'Hoxton.SR1'} | ${'Hoxton.sr-1'} | ${0} + ${'1.1'} | ${'1.2'} | ${-1} + ${'1.a'} | ${'1.1'} | ${-1} + ${'1.A'} | ${'1.B'} | ${-1} + ${'1.B'} | ${'1.a'} | ${-1} + ${'1.a'} | ${'1.b'} | ${-1} + ${'1.1'} | ${'1.1.0'} | ${-1} + ${'1.1.a'} | ${'1.1'} | ${-1} + ${'1.0-dev'} | ${'1.0-alpha'} | ${-1} + ${'1.0-alpha'} | ${'1.0-rc'} | ${-1} + ${'1.0-zeta'} | ${'1.0-rc'} | ${-1} + ${'1.0-rc'} | ${'1.0-final'} | ${-1} + ${'1.0-final'} | ${'1.0-ga'} | ${-1} + ${'1.0-ga'} | ${'1.0-release'} | ${-1} + ${'1.0-rc'} | ${'1.0-release'} | ${-1} + ${'1.0-final'} | ${'1.0'} | ${-1} + ${'1.0-alpha'} | ${'1.0-SNAPSHOT'} | ${-1} + ${'1.0-zeta'} | ${'1.0-SNAPSHOT'} | ${-1} + ${'1.0-zeta'} | ${'1.0-rc'} | ${-1} + ${'1.0-rc'} | ${'1.0'} | ${-1} + ${'1.0-preview'} | ${'1.0'} | ${-1} + ${'1.0'} | ${'1.0-20150201.121010-123'} | ${-1} + ${'1.0-20150201.121010-123'} | ${'1.1'} | ${-1} + ${'Hoxton.RELEASE'} | ${'Hoxton.SR1'} | ${-1} + ${'1.0-release'} | ${'1.0-sp-1'} | ${-1} + ${'1.0-sp-1'} | ${'1.0-sp-2'} | ${-1} + ${'1.2'} | ${'1.1'} | ${1} + ${'1.1'} | ${'1.1.a'} | ${1} + ${'1.B'} | ${'1.A'} | ${1} + ${'1.a'} | ${'1.B'} | ${1} + ${'1.b'} | ${'1.a'} | ${1} + ${'1.1.0'} | ${'1.1'} | ${1} + ${'1.1'} | ${'1.a'} | ${1} + ${'1.0-alpha'} | ${'1.0-dev'} | ${1} + ${'1.0-rc'} | ${'1.0-alpha'} | ${1} + ${'1.0-rc'} | ${'1.0-zeta'} | ${1} + ${'1.0-release'} | ${'1.0-rc'} | ${1} + ${'1.0-final'} | ${'1.0-rc'} | ${1} + ${'1.0-ga'} | ${'1.0-final'} | ${1} + ${'1.0-release'} | ${'1.0-ga'} | ${1} + ${'1.0-release'} | ${'1.0-final'} | ${1} + ${'1.0'} | ${'1.0-final'} | ${1} + ${'1.0-SNAPSHOT'} | ${'1.0-alpha'} | ${1} + ${'1.0-SNAPSHOT'} | ${'1.0-zeta'} | ${1} + ${'1.0-rc'} | ${'1.0-zeta'} | ${1} + ${'1.0'} | ${'1.0-rc'} | ${1} + ${'1.0'} | ${'1.0-preview'} | ${1} + ${'1.0-20150201.121010-123'} | ${'1.0'} | ${1} + ${'1.1'} | ${'1.0-20150201.121010-123'} | ${1} + ${'Hoxton.SR1'} | ${'Hoxton.RELEASE'} | ${1} + ${'1.0-sp-1'} | ${'1.0-release'} | ${1} + ${'1.0-sp-2'} | ${'1.0-sp-1'} | ${1} + ${''} | ${''} | ${0} + ${'384.vf35b_f26814ec'} | ${'400.v35420b_922dcb_'} | ${-1} + ${'___'} | ${'...'} | ${0} + `('compare("$a", "$b") === $expected', ({ a, b, expected }) => { + expect(compare(a, b)).toEqual(expected); + }); }); - it.each` - rangeStr - ${''} - ${'1.2.3-SNAPSHOT'} - ${'1.2..+'} - ${'1.2.++'} - `('parsePrefixRange("$rangeStr") is null', ({ rangeStr }) => { - const range = parsePrefixRange(rangeStr); - expect(range).toBeNull(); + describe('parsePrefixRange', () => { + it.each` + rangeStr + ${''} + ${'1.2.3-SNAPSHOT'} + ${'1.2..+'} + ${'1.2.++'} + `('parsePrefixRange("$rangeStr") is null', ({ rangeStr }) => { + const range = parsePrefixRange(rangeStr); + expect(range).toBeNull(); + }); }); - it.each` - rangeStr - ${''} - ${'1.2.3-SNAPSHOT'} - ${'[]'} - ${'('} - ${'['} - ${','} - ${'[1.0'} - ${'1.0]'} - ${'[1.0],'} - ${',[1.0]'} - ${'[2.0,1.0)'} - ${'[1.2,1.3],1.4'} - ${'[1.2,,1.3]'} - ${'[1,[2,3],4]'} - ${'[1.3,1.2]'} - `('parseMavenBasedRange("$rangeStr") is null', ({ rangeStr }) => { - const range = parseMavenBasedRange(rangeStr); - expect(range).toBeNull(); + describe('parseMavenBasedRange', () => { + it.each` + rangeStr + ${''} + ${'1.2.3-SNAPSHOT'} + ${'[]'} + ${'('} + ${'['} + ${','} + ${'[1.0'} + ${'1.0]'} + ${'[1.0],'} + ${',[1.0]'} + ${'[2.0,1.0)'} + ${'[1.2,1.3],1.4'} + ${'[1.2,,1.3]'} + ${'[1,[2,3],4]'} + ${'[1.3,1.2]'} + `('parseMavenBasedRange("$rangeStr") is null', ({ rangeStr }) => { + const range = parseMavenBasedRange(rangeStr); + expect(range).toBeNull(); + }); }); - it.each` - input | expected - ${'1.0.0'} | ${true} - ${'[1.12.6,1.18.6]'} | ${true} - ${undefined} | ${false} - `('isValid("$input") === $expected', ({ input, expected }) => { - expect(api.isValid(input)).toBe(expected); + describe('isValid', () => { + it.each` + input | expected + ${'1.0.0'} | ${true} + ${'[1.0.0]'} | ${true} + ${'[1..2]'} | ${false} + ${'[1.12.6,1.18.6]'} | ${true} + ${undefined} | ${false} + `('isValid("$input") === $expected', ({ input, expected }) => { + expect(api.isValid(input)).toBe(expected); + }); }); - it.each` - input | expected - ${''} | ${false} - ${'latest.integration'} | ${false} - ${'latest.release'} | ${false} - ${'latest'} | ${false} - ${'1'} | ${true} - ${'a'} | ${true} - ${'A'} | ${true} - ${'1a1'} | ${true} - ${'1.a.1'} | ${true} - ${'1-a-1'} | ${true} - ${'1_a_1'} | ${true} - ${'1+a+1'} | ${true} - ${'1!a!1'} | ${false} - ${'1.0-20150201.121010-123'} | ${true} - ${'dev'} | ${true} - ${'rc'} | ${true} - ${'release'} | ${true} - ${'final'} | ${true} - ${'SNAPSHOT'} | ${true} - ${'1.2'} | ${true} - ${'1..2'} | ${false} - ${'1++2'} | ${false} - ${'1--2'} | ${false} - ${'1__2'} | ${false} - `('isVersion("$input") === $expected', ({ input, expected }) => { - expect(api.isVersion(input)).toBe(expected); + describe('isVersion', () => { + it.each` + input | expected + ${''} | ${false} + ${'latest.integration'} | ${false} + ${'latest.release'} | ${false} + ${'latest'} | ${false} + ${'1'} | ${true} + ${'a'} | ${true} + ${'A'} | ${true} + ${'1a1'} | ${true} + ${'1.a.1'} | ${true} + ${'1-a-1'} | ${true} + ${'1_a_1'} | ${true} + ${'1+a+1'} | ${true} + ${'1!a!1'} | ${false} + ${'1.0-20150201.121010-123'} | ${true} + ${'dev'} | ${true} + ${'rc'} | ${true} + ${'release'} | ${true} + ${'final'} | ${true} + ${'SNAPSHOT'} | ${true} + ${'1.2'} | ${true} + ${'1..2'} | ${false} + ${'1++2'} | ${false} + ${'1--2'} | ${false} + ${'1__2'} | ${false} + ${'400.v35420b_922dcb_'} | ${true} + ${'400.v35420b_922dcb'} | ${true} + ${'__'} | ${false} + ${'_.'} | ${false} + ${'._'} | ${false} + ${'_+'} | ${false} + ${'+.'} | ${false} + ${'.+'} | ${false} + `('isVersion("$input") === $expected', ({ input, expected }) => { + expect(api.isVersion(input)).toBe(expected); + }); }); - it.each` - input | expected - ${''} | ${false} - ${'latest'} | ${false} - ${'foobar'} | ${true} - ${'final'} | ${true} - ${'1'} | ${true} - ${'1..2'} | ${false} - ${'1.2'} | ${true} - ${'1.2.3'} | ${true} - ${'1.2.3.4 s'} | ${false} - ${'1.2.3.4'} | ${true} - ${'v1.2.3.4'} | ${true} - ${'1-alpha-1'} | ${false} - ${'1-b1'} | ${false} - ${'1-foo'} | ${true} - ${'1-final-1.0.0'} | ${true} - ${'1-release'} | ${true} - ${'1.final'} | ${true} - ${'1.0milestone1'} | ${false} - ${'1-sp'} | ${true} - ${'1-ga-1'} | ${true} - ${'1.3-groovy-2.5'} | ${true} - ${'1.3-RC1-groovy-2.5'} | ${false} - ${'1-preview'} | ${false} - ${'Hoxton.RELEASE'} | ${true} - ${'Hoxton.SR'} | ${true} - ${'Hoxton.SR1'} | ${true} - ${'1.3.5-native-mt-1.3.71-release-429'} | ${false} - ${'1.0-dev'} | ${false} - `('isStable("$input") === $expected', ({ input, expected }) => { - expect(api.isStable(input)).toBe(expected); + describe('isStable', () => { + it.each` + input | expected + ${''} | ${false} + ${'latest'} | ${false} + ${'foobar'} | ${true} + ${'final'} | ${true} + ${'1'} | ${true} + ${'1..2'} | ${false} + ${'1.2'} | ${true} + ${'1.2.3'} | ${true} + ${'1.2.3.4 s'} | ${false} + ${'1.2.3.4'} | ${true} + ${'v1.2.3.4'} | ${true} + ${'1-alpha-1'} | ${false} + ${'1-b1'} | ${false} + ${'1-foo'} | ${true} + ${'1-final-1.0.0'} | ${true} + ${'1-release'} | ${true} + ${'1.final'} | ${true} + ${'1.0milestone1'} | ${false} + ${'1-sp'} | ${true} + ${'1-ga-1'} | ${true} + ${'1.3-groovy-2.5'} | ${true} + ${'1.3-RC1-groovy-2.5'} | ${false} + ${'1-preview'} | ${false} + ${'Hoxton.RELEASE'} | ${true} + ${'Hoxton.SR'} | ${true} + ${'Hoxton.SR1'} | ${true} + ${'1.3.5-native-mt-1.3.71-release-429'} | ${false} + ${'1.0-dev'} | ${false} + `('isStable("$input") === $expected', ({ input, expected }) => { + expect(api.isStable(input)).toBe(expected); + }); }); - it.each` - input | major | minor | patch - ${''} | ${null} | ${null} | ${null} - ${'1'} | ${1} | ${0} | ${0} - ${'1.2'} | ${1} | ${2} | ${0} - ${'1.2.3'} | ${1} | ${2} | ${3} - ${'v1.2.3'} | ${1} | ${2} | ${3} - ${'1.2.3.4'} | ${1} | ${2} | ${3} - ${'1rc42'} | ${1} | ${0} | ${0} - ${'1-rc10'} | ${1} | ${0} | ${0} - ${'1-rc42'} | ${1} | ${0} | ${0} - ${'1-rc42-1'} | ${1} | ${0} | ${0} - `( - '"$input" is represented as [$major, $minor, $patch]', - ({ input, major, minor, patch }) => { - expect(api.getMajor(input)).toBe(major); - expect(api.getMinor(input)).toBe(minor); - expect(api.getPatch(input)).toBe(patch); - }, - ); + describe('major-minor-patch', () => { + it.each` + input | major | minor | patch + ${''} | ${null} | ${null} | ${null} + ${'1'} | ${1} | ${0} | ${0} + ${'1.2'} | ${1} | ${2} | ${0} + ${'1.2.3'} | ${1} | ${2} | ${3} + ${'v1.2.3'} | ${1} | ${2} | ${3} + ${'1.2.3.4'} | ${1} | ${2} | ${3} + ${'1rc42'} | ${1} | ${0} | ${0} + ${'1-rc10'} | ${1} | ${0} | ${0} + ${'1-rc42'} | ${1} | ${0} | ${0} + ${'1-rc42-1'} | ${1} | ${0} | ${0} + `( + '"$input" is represented as [$major, $minor, $patch]', + ({ input, major, minor, patch }) => { + expect(api.getMajor(input)).toBe(major); + expect(api.getMinor(input)).toBe(minor); + expect(api.getPatch(input)).toBe(patch); + }, + ); + }); - it.each` - version | range | expected - ${'1'} | ${'[[]]'} | ${false} - ${'0'} | ${'[0,1]'} | ${true} - ${'1'} | ${'[0,1]'} | ${true} - ${'0'} | ${'(0,1)'} | ${false} - ${'1'} | ${'(0,1)'} | ${false} - ${'1'} | ${'(0,2)'} | ${true} - ${'1'} | ${'[0,2]'} | ${true} - ${'1'} | ${'(,1]'} | ${true} - ${'1'} | ${'(,1)'} | ${false} - ${'1'} | ${'[1,)'} | ${true} - ${'1'} | ${'(1,)'} | ${false} - ${'0'} | ${''} | ${false} - ${'1'} | ${'1'} | ${true} - ${'1.2.3'} | ${'1.2.+'} | ${true} - ${'1.2.3.4'} | ${'1.2.+'} | ${true} - ${'1.3.0'} | ${'1.2.+'} | ${false} - ${'foo'} | ${'+'} | ${true} - ${'1'} | ${'+'} | ${true} - ${'99999999999'} | ${'+'} | ${true} - `( - 'matches("$version", "$range") === $expected', - ({ version, range, expected }) => { - expect(api.matches(version, range)).toBe(expected); - }, - ); + describe('matches', () => { + it.each` + version | range | expected + ${'1'} | ${'[[]]'} | ${false} + ${'0'} | ${'[0,1]'} | ${true} + ${'1'} | ${'[0,1]'} | ${true} + ${'0'} | ${'(0,1)'} | ${false} + ${'1'} | ${'(0,1)'} | ${false} + ${'1'} | ${'(0,2)'} | ${true} + ${'1'} | ${'[0,2]'} | ${true} + ${'1'} | ${'(,1]'} | ${true} + ${'1'} | ${'(,1)'} | ${false} + ${'1'} | ${'[1,)'} | ${true} + ${'1'} | ${'(1,)'} | ${false} + ${'0'} | ${''} | ${false} + ${'1'} | ${'1'} | ${true} + ${'1.2.3'} | ${'1.2.+'} | ${true} + ${'1.2.3.4'} | ${'1.2.+'} | ${true} + ${'1.3.0'} | ${'1.2.+'} | ${false} + ${'foo'} | ${'+'} | ${true} + ${'1'} | ${'+'} | ${true} + ${'99999999999'} | ${'+'} | ${true} + ${'1.2.3'} | ${'[1.2.3]'} | ${true} + ${'1.2.3'} | ${'[1.2.4]'} | ${false} + `( + 'matches("$version", "$range") === $expected', + ({ version, range, expected }) => { + expect(api.matches(version, range)).toBe(expected); + }, + ); + }); - it.each` - a | b | expected - ${'1.1'} | ${'1'} | ${true} - `('isGreaterThan("$a", "$b") === $expected', ({ a, b, expected }) => { - expect(api.isGreaterThan(a, b)).toBe(expected); + describe('isGreaterThan', () => { + it.each` + a | b | expected + ${'1.1'} | ${'1'} | ${true} + `('isGreaterThan("$a", "$b") === $expected', ({ a, b, expected }) => { + expect(api.isGreaterThan(a, b)).toBe(expected); + }); }); - it.each` - versions | range | expected - ${['0', '1.5', '1', '2']} | ${'1.+'} | ${'1'} - `( - 'minSatisfyingVersion($versions, "$range") === $expected', - ({ versions, range, expected }) => { - expect(api.minSatisfyingVersion(versions, range)).toBe(expected); - }, - ); + describe('minSatisfyingVersion', () => { + it.each` + versions | range | expected + ${['0', '1.5', '1', '2']} | ${'1.+'} | ${'1'} + `( + 'minSatisfyingVersion($versions, "$range") === $expected', + ({ versions, range, expected }) => { + expect(api.minSatisfyingVersion(versions, range)).toBe(expected); + }, + ); + }); - it.each` - versions | range | expected - ${['0', '1', '1.5', '2']} | ${'1.+'} | ${'1.5'} - `( - 'getSatisfyingVersion($versions, "$range") === $expected', - ({ versions, range, expected }) => { - expect(api.getSatisfyingVersion(versions, range)).toBe(expected); - }, - ); + describe('getSatisfyingVersion', () => { + it.each` + versions | range | expected + ${['0', '1', '1.5', '2']} | ${'1.+'} | ${'1.5'} + `( + 'getSatisfyingVersion($versions, "$range") === $expected', + ({ versions, range, expected }) => { + expect(api.getSatisfyingVersion(versions, range)).toBe(expected); + }, + ); + }); - it.each` - currentValue | rangeStrategy | currentVersion | newVersion | expected - ${'1'} | ${null} | ${null} | ${'1.1'} | ${'1.1'} - ${'[1.2.3,]'} | ${null} | ${null} | ${'1.2.4'} | ${'[1.2.3,]'} - ${'[1.2.3,2)'} | ${null} | ${null} | ${'2.0.0'} | ${'[1.2.3,3)'} - ${'[1.3,1.4)'} | ${null} | ${null} | ${'2.0.0'} | ${'[2.0,3.0)'} - ${'[1.3,1.4)'} | ${null} | ${null} | ${'1.5.1'} | ${'[1.5,1.6)'} - ${'[1,1.4)'} | ${null} | ${null} | ${'1.5.1'} | ${'[1,1.6)'} - ${'[1.3,2)'} | ${null} | ${null} | ${'1.4.0'} | ${'[1.3,2)'} - ${'1.?'} | ${null} | ${null} | ${'2'} | ${'1.?'} - ${'1..'} | ${null} | ${null} | ${'2'} | ${'1..'} - ${'1--'} | ${null} | ${null} | ${'2'} | ${'1--'} - ${'+'} | ${null} | ${null} | ${'1.2.4'} | ${null} - ${'1.+'} | ${null} | ${null} | ${'1.2.4'} | ${'1.+'} - ${'1.+'} | ${null} | ${null} | ${'2.1.2'} | ${'2.+'} - ${'1.+'} | ${null} | ${null} | ${'2'} | ${'2.+'} - ${'1.3.+'} | ${null} | ${null} | ${'1.3.4'} | ${'1.3.+'} - ${'1.3.+'} | ${null} | ${null} | ${'1.5.2'} | ${'1.5.+'} - ${'1.3.+'} | ${null} | ${null} | ${'2'} | ${'2'} - ${'[1.2.3]'} | ${'pin'} | ${'1.2.3'} | ${'1.2.4'} | ${'1.2.4'} - ${'[1.0.0,1.2.3]'} | ${'pin'} | ${'1.0.0'} | ${'1.2.4'} | ${'1.2.4'} - ${'[1.0.0,1.2.23]'} | ${'pin'} | ${'1.0.0'} | ${'1.2.23'} | ${'1.2.23'} - ${'(,1.0]'} | ${'pin'} | ${'0.0.1'} | ${'2.0'} | ${'2.0'} - ${'],1.0]'} | ${'pin'} | ${'0.0.1'} | ${'2.0'} | ${'2.0'} - ${'(,1.0)'} | ${'pin'} | ${'0.1'} | ${'2.0'} | ${'2.0'} - ${'],1.0['} | ${'pin'} | ${'2.0'} | ${'],2.0['} | ${'],2.0['} - ${'[1.0,1.2],[1.3,1.5)'} | ${'pin'} | ${'1.0'} | ${'1.2.4'} | ${'1.2.4'} - ${'[1.0,1.2],[1.3,1.5['} | ${'pin'} | ${'1.0'} | ${'1.2.4'} | ${'1.2.4'} - ${'[1.2.3,)'} | ${'pin'} | ${'1.2.3'} | ${'1.2.4'} | ${'1.2.4'} - ${'[1.2.3,['} | ${'pin'} | ${'1.2.3'} | ${'1.2.4'} | ${'1.2.4'} - `( - 'getNewValue($currentValue, $rangeStrategy, $currentVersion, $newVersion, $expected) === $expected', - ({ currentValue, rangeStrategy, currentVersion, newVersion, expected }) => { - const res = api.getNewValue({ + describe('getNewValue', () => { + it.each` + currentValue | rangeStrategy | currentVersion | newVersion | expected + ${'1'} | ${null} | ${null} | ${'1.1'} | ${'1.1'} + ${'[1.2.3,]'} | ${null} | ${null} | ${'1.2.4'} | ${'[1.2.3,]'} + ${'[1.2.3,2)'} | ${null} | ${null} | ${'2.0.0'} | ${'[1.2.3,3)'} + ${'[1.3,1.4)'} | ${null} | ${null} | ${'2.0.0'} | ${'[2.0,3.0)'} + ${'[1.3,1.4)'} | ${null} | ${null} | ${'1.5.1'} | ${'[1.5,1.6)'} + ${'[1,1.4)'} | ${null} | ${null} | ${'1.5.1'} | ${'[1,1.6)'} + ${'[1.3,2)'} | ${null} | ${null} | ${'1.4.0'} | ${'[1.3,2)'} + ${'1.?'} | ${null} | ${null} | ${'2'} | ${'1.?'} + ${'1..'} | ${null} | ${null} | ${'2'} | ${'1..'} + ${'1--'} | ${null} | ${null} | ${'2'} | ${'1--'} + ${'+'} | ${null} | ${null} | ${'1.2.4'} | ${null} + ${'1.+'} | ${null} | ${null} | ${'1.2.4'} | ${'1.+'} + ${'1.+'} | ${null} | ${null} | ${'2.1.2'} | ${'2.+'} + ${'1.+'} | ${null} | ${null} | ${'2'} | ${'2.+'} + ${'1.3.+'} | ${null} | ${null} | ${'1.3.4'} | ${'1.3.+'} + ${'1.3.+'} | ${null} | ${null} | ${'1.5.2'} | ${'1.5.+'} + ${'1.3.+'} | ${null} | ${null} | ${'2'} | ${'2'} + ${'[1.2.3]'} | ${'pin'} | ${'1.2.3'} | ${'1.2.4'} | ${'1.2.4'} + ${'[1.0.0,1.2.3]'} | ${'pin'} | ${'1.0.0'} | ${'1.2.4'} | ${'1.2.4'} + ${'[1.0.0,1.2.23]'} | ${'pin'} | ${'1.0.0'} | ${'1.2.23'} | ${'1.2.23'} + ${'(,1.0]'} | ${'pin'} | ${'0.0.1'} | ${'2.0'} | ${'2.0'} + ${'],1.0]'} | ${'pin'} | ${'0.0.1'} | ${'2.0'} | ${'2.0'} + ${'(,1.0)'} | ${'pin'} | ${'0.1'} | ${'2.0'} | ${'2.0'} + ${'],1.0['} | ${'pin'} | ${'2.0'} | ${'],2.0['} | ${'],2.0['} + ${'[1.0,1.2],[1.3,1.5)'} | ${'pin'} | ${'1.0'} | ${'1.2.4'} | ${'1.2.4'} + ${'[1.0,1.2],[1.3,1.5['} | ${'pin'} | ${'1.0'} | ${'1.2.4'} | ${'1.2.4'} + ${'[1.2.3,)'} | ${'pin'} | ${'1.2.3'} | ${'1.2.4'} | ${'1.2.4'} + ${'[1.2.3,['} | ${'pin'} | ${'1.2.3'} | ${'1.2.4'} | ${'1.2.4'} + `( + 'getNewValue($currentValue, $rangeStrategy, $currentVersion, $newVersion, $expected) === $expected', + ({ currentValue, rangeStrategy, currentVersion, newVersion, - }); - expect(res).toBe(expected); - }, - ); + expected, + }) => { + const res = api.getNewValue({ + currentValue, + rangeStrategy, + currentVersion, + newVersion, + }); + expect(res).toBe(expected); + }, + ); + }); }); diff --git a/lib/modules/versioning/gradle/index.ts b/lib/modules/versioning/gradle/index.ts index b6ef922c4aeab7..6b17ca2a3acb7f 100644 --- a/lib/modules/versioning/gradle/index.ts +++ b/lib/modules/versioning/gradle/index.ts @@ -10,6 +10,7 @@ import { parse, parseMavenBasedRange, parsePrefixRange, + parseSingleVersionRange, } from './compare'; export const id = 'gradle'; @@ -114,6 +115,12 @@ const matches = (a: string, b: string): boolean => { return equals(a, b); } + const singleVersionRange = parseSingleVersionRange(b); + if (singleVersionRange) { + const { val } = singleVersionRange; + return equals(a, val); + } + const prefixRange = parsePrefixRange(b); if (prefixRange) { const tokens = prefixRange.tokens; diff --git a/lib/modules/versioning/maven/index.spec.ts b/lib/modules/versioning/maven/index.spec.ts index f23b1ce9abf682..3507199d9adc24 100644 --- a/lib/modules/versioning/maven/index.spec.ts +++ b/lib/modules/versioning/maven/index.spec.ts @@ -11,6 +11,7 @@ describe('modules/versioning/maven/index', () => { it.each` version | expected ${'1.0.0'} | ${true} + ${'[1.0.0]'} | ${true} ${'17.0.5+8'} | ${true} ${'[1.12.6,1.18.6]'} | ${true} ${undefined} | ${false} @@ -119,6 +120,8 @@ describe('modules/versioning/maven/index', () => { ${'2.4.2'} | ${'2.4.2'} | ${true} ${'2.4.2'} | ${'= 2.4.2'} | ${false} ${'1.2.3'} | ${'[1,2],[3,4]'} | ${true} + ${'1.2.3'} | ${'[1.2.3]'} | ${true} + ${'1.2.3'} | ${'[1.2.4]'} | ${false} `( 'matches("$version", "$range") === $expected', ({ version, range, expected }) => { diff --git a/lib/modules/versioning/pep440/range.ts b/lib/modules/versioning/pep440/range.ts index a4f4c5b26bb717..b450219bb72532 100644 --- a/lib/modules/versioning/pep440/range.ts +++ b/lib/modules/versioning/pep440/range.ts @@ -121,7 +121,7 @@ export function getNewValue({ if (!ranges.length) { // an empty string is an allowed value for PEP440 range // it means get any version - logger.warn('Empty currentValue: ' + currentValue); + logger.warn({ currentValue }, 'Empty currentValue'); return currentValue; } } catch (err) { diff --git a/lib/modules/versioning/pvp/index.spec.ts b/lib/modules/versioning/pvp/index.spec.ts index 900baa3ae7ef8e..de36293d2df97e 100644 --- a/lib/modules/versioning/pvp/index.spec.ts +++ b/lib/modules/versioning/pvp/index.spec.ts @@ -141,12 +141,12 @@ describe('modules/versioning/pvp/index', () => { describe('.getNewValue(newValueConfig)', () => { it.each` currentValue | newVersion | rangeStrategy | expected - ${'>=1.0 && <1.1'} | ${'1.1'} | ${'auto'} | ${'>=1.0 && <1.2'} - ${'>=1.2 && <1.3'} | ${'1.2.3'} | ${'auto'} | ${null} + ${'>=1.0 && <1.1'} | ${'1.1'} | ${'widen'} | ${'>=1.0 && <1.2'} + ${'>=1.2 && <1.3'} | ${'1.2.3'} | ${'widen'} | ${null} ${'>=1.0 && <1.1'} | ${'1.2.3'} | ${'update-lockfile'} | ${null} - ${'gibberish'} | ${'1.2.3'} | ${'auto'} | ${null} - ${'>=1.0 && <1.1'} | ${'0.9'} | ${'auto'} | ${null} - ${'>=1.0 && <1.1'} | ${''} | ${'auto'} | ${null} + ${'gibberish'} | ${'1.2.3'} | ${'widen'} | ${null} + ${'>=1.0 && <1.1'} | ${'0.9'} | ${'widen'} | ${null} + ${'>=1.0 && <1.1'} | ${''} | ${'widen'} | ${null} `( 'pvp.getNewValue({currentValue: "$currentValue", newVersion: "$newVersion", rangeStrategy: "$rangeStrategy"}) === $expected', ({ currentValue, newVersion, rangeStrategy, expected }) => { diff --git a/lib/modules/versioning/pvp/index.ts b/lib/modules/versioning/pvp/index.ts index 59e8b3026ab91c..ca6c5953796ebc 100644 --- a/lib/modules/versioning/pvp/index.ts +++ b/lib/modules/versioning/pvp/index.ts @@ -9,7 +9,7 @@ export const id = 'pvp'; export const displayName = 'Package Versioning Policy (Haskell)'; export const urls = ['https://pvp.haskell.org']; export const supportsRanges = true; -export const supportedRangeStrategies: RangeStrategy[] = ['auto']; +export const supportedRangeStrategies: RangeStrategy[] = ['widen']; const digitsAndDots = regEx(/^[\d.]+$/); @@ -112,7 +112,7 @@ function getNewValue({ newVersion, rangeStrategy, }: NewValueConfig): string | null { - if (rangeStrategy !== 'auto') { + if (rangeStrategy !== 'widen') { logger.info( { rangeStrategy, currentValue, newVersion }, `PVP can't handle this range strategy.`, diff --git a/lib/modules/versioning/schema.ts b/lib/modules/versioning/schema.ts index 3bf4db93da042a..b7d55562f67278 100644 --- a/lib/modules/versioning/schema.ts +++ b/lib/modules/versioning/schema.ts @@ -12,8 +12,7 @@ export const Versioning = z let versioning = versionings.get(versioningName); if (!versioning) { - logger.info( - { versioning: versioningSpec }, + logger.debug( `Versioning: '${versioningSpec}' not found, falling back to ${defaultVersioning.id}`, ); return defaultVersioning.api; diff --git a/lib/modules/versioning/ubuntu/common.ts b/lib/modules/versioning/ubuntu/common.ts index abcb173c0486ea..0cd4b958c7f392 100644 --- a/lib/modules/versioning/ubuntu/common.ts +++ b/lib/modules/versioning/ubuntu/common.ts @@ -1,11 +1,13 @@ import { regEx } from '../../../util/regex'; +const regex = regEx(/^(?\w+)-(?\d{8})(?\.\d{1,2})?$/); + function isDatedCodeName(input: string): boolean { - return regEx(/^(?\w+)-(?\d{8})$/).test(input); + return regex.test(input); } function getDatedContainerImageCodename(version: string): null | string { - const groups = regEx(/^(?\w+)-(?\d{8})$/).exec(version); + const groups = regex.exec(version); if (!groups?.groups) { return null; } @@ -13,7 +15,7 @@ function getDatedContainerImageCodename(version: string): null | string { } function getDatedContainerImageVersion(version: string): null | number { - const groups = regEx(/^(?\w+)-(?\d{8})$/).exec(version); + const groups = regex.exec(version); if (!groups?.groups) { return null; } @@ -21,8 +23,18 @@ function getDatedContainerImageVersion(version: string): null | number { return parseInt(groups.groups.date, 10); } +function getDatedContainerImageSuffix(version: string): null | string { + const groups = regex.exec(version); + if (!groups?.groups?.suffix) { + return null; + } + + return groups.groups.suffix; +} + export { isDatedCodeName, getDatedContainerImageCodename, getDatedContainerImageVersion, + getDatedContainerImageSuffix, }; diff --git a/lib/modules/versioning/ubuntu/index.spec.ts b/lib/modules/versioning/ubuntu/index.spec.ts index eaa5de4e2842bf..5a60006fa9e83e 100644 --- a/lib/modules/versioning/ubuntu/index.spec.ts +++ b/lib/modules/versioning/ubuntu/index.spec.ts @@ -5,84 +5,88 @@ describe('modules/versioning/ubuntu/index', () => { const dt = DateTime.fromISO('2022-04-20'); it.each` - version | expected - ${undefined} | ${false} - ${null} | ${false} - ${''} | ${false} - ${'xenial'} | ${true} - ${'04.10'} | ${true} - ${'05.04'} | ${true} - ${'05.10'} | ${true} - ${'6.06'} | ${true} - ${'6.10'} | ${true} - ${'7.04'} | ${true} - ${'7.10'} | ${true} - ${'8.04'} | ${true} - ${'8.10'} | ${true} - ${'9.04'} | ${true} - ${'9.10'} | ${true} - ${'10.04.4'} | ${true} - ${'10.10'} | ${true} - ${'11.04'} | ${true} - ${'11.10'} | ${true} - ${'12.04.5'} | ${true} - ${'12.10'} | ${true} - ${'13.04'} | ${true} - ${'13.10'} | ${true} - ${'14.04.6'} | ${true} - ${'14.10'} | ${true} - ${'15.04'} | ${true} - ${'15.10'} | ${true} - ${'16.04.7'} | ${true} - ${'16.10'} | ${true} - ${'17.04'} | ${true} - ${'17.10'} | ${true} - ${'18.04.5'} | ${true} - ${'18.10'} | ${true} - ${'19.04'} | ${true} - ${'19.10'} | ${true} - ${'20.04'} | ${true} - ${'20.10'} | ${true} - ${'2020.04'} | ${false} - ${'xenial'} | ${true} - ${'warty'} | ${true} - ${'hoary'} | ${true} - ${'breezy'} | ${true} - ${'dapper'} | ${true} - ${'edgy'} | ${true} - ${'feisty'} | ${true} - ${'gutsy'} | ${true} - ${'hardy'} | ${true} - ${'intrepid'} | ${true} - ${'jaunty'} | ${true} - ${'karmic'} | ${true} - ${'lucid.4'} | ${false} - ${'maverick'} | ${true} - ${'natty'} | ${true} - ${'oneiric'} | ${true} - ${'precise.5'} | ${false} - ${'quantal'} | ${true} - ${'raring'} | ${true} - ${'saucy'} | ${true} - ${'trusty.6'} | ${false} - ${'utopic'} | ${true} - ${'vivid'} | ${true} - ${'wily'} | ${true} - ${'xenial.7'} | ${false} - ${'yakkety'} | ${true} - ${'zesty'} | ${true} - ${'artful'} | ${true} - ${'bionic.5'} | ${false} - ${'cosmic'} | ${true} - ${'disco'} | ${true} - ${'eoan'} | ${true} - ${'focal'} | ${true} - ${'groovy'} | ${true} - ${'hirsute'} | ${true} - ${'impish'} | ${true} - ${'jammy'} | ${true} - ${'jammy-20230816'} | ${true} - ${'jammy-2023086'} | ${false} + version | expected + ${undefined} | ${false} + ${null} | ${false} + ${''} | ${false} + ${'xenial'} | ${true} + ${'04.10'} | ${true} + ${'05.04'} | ${true} + ${'05.10'} | ${true} + ${'6.06'} | ${true} + ${'6.10'} | ${true} + ${'7.04'} | ${true} + ${'7.10'} | ${true} + ${'8.04'} | ${true} + ${'8.10'} | ${true} + ${'9.04'} | ${true} + ${'9.10'} | ${true} + ${'10.04.4'} | ${true} + ${'10.10'} | ${true} + ${'11.04'} | ${true} + ${'11.10'} | ${true} + ${'12.04.5'} | ${true} + ${'12.10'} | ${true} + ${'13.04'} | ${true} + ${'13.10'} | ${true} + ${'14.04.6'} | ${true} + ${'14.10'} | ${true} + ${'15.04'} | ${true} + ${'15.10'} | ${true} + ${'16.04.7'} | ${true} + ${'16.10'} | ${true} + ${'17.04'} | ${true} + ${'17.10'} | ${true} + ${'18.04.5'} | ${true} + ${'18.10'} | ${true} + ${'19.04'} | ${true} + ${'19.10'} | ${true} + ${'20.04'} | ${true} + ${'20.10'} | ${true} + ${'2020.04'} | ${false} + ${'xenial'} | ${true} + ${'warty'} | ${true} + ${'hoary'} | ${true} + ${'breezy'} | ${true} + ${'dapper'} | ${true} + ${'edgy'} | ${true} + ${'feisty'} | ${true} + ${'gutsy'} | ${true} + ${'hardy'} | ${true} + ${'intrepid'} | ${true} + ${'jaunty'} | ${true} + ${'karmic'} | ${true} + ${'lucid.4'} | ${false} + ${'maverick'} | ${true} + ${'natty'} | ${true} + ${'oneiric'} | ${true} + ${'precise.5'} | ${false} + ${'quantal'} | ${true} + ${'raring'} | ${true} + ${'saucy'} | ${true} + ${'trusty.6'} | ${false} + ${'utopic'} | ${true} + ${'vivid'} | ${true} + ${'wily'} | ${true} + ${'xenial.7'} | ${false} + ${'yakkety'} | ${true} + ${'zesty'} | ${true} + ${'artful'} | ${true} + ${'bionic.5'} | ${false} + ${'cosmic'} | ${true} + ${'disco'} | ${true} + ${'eoan'} | ${true} + ${'focal'} | ${true} + ${'groovy'} | ${true} + ${'hirsute'} | ${true} + ${'impish'} | ${true} + ${'jammy'} | ${true} + ${'jammy-20230816'} | ${true} + ${'jammy-20230816'} | ${true} + ${'yakkety-20160806.1'} | ${true} + ${'utopic-20150228.11'} | ${true} + ${'utopic-20150228.11.1'} | ${false} + ${'oracular-20240811.'} | ${false} `('isValid("$version") === $expected', ({ version, expected }) => { expect(ubuntu.isValid(version)).toBe(expected); }); @@ -272,51 +276,59 @@ describe('modules/versioning/ubuntu/index', () => { ); it.each` - a | b | expected - ${'20.04'} | ${'2020.04'} | ${false} - ${'17.10'} | ${'artful'} | ${true} - ${'xenial'} | ${'artful'} | ${false} - ${'17.04'} | ${'artful'} | ${false} - ${'artful'} | ${'17.10'} | ${true} - ${'16.04'} | ${'xenial'} | ${true} - ${'focal'} | ${'20.04'} | ${true} - ${'20.04'} | ${'focal'} | ${true} - ${'19.10'} | ${'19.10'} | ${true} - ${'jammy'} | ${'jammy-20230816'} | ${false} - ${'jammy-20230816'} | ${'jammy-20230816'} | ${true} - ${'jammy-20230716'} | ${'jammy-20230816'} | ${false} + a | b | expected + ${'20.04'} | ${'2020.04'} | ${false} + ${'17.10'} | ${'artful'} | ${true} + ${'xenial'} | ${'artful'} | ${false} + ${'17.04'} | ${'artful'} | ${false} + ${'artful'} | ${'17.10'} | ${true} + ${'16.04'} | ${'xenial'} | ${true} + ${'focal'} | ${'20.04'} | ${true} + ${'20.04'} | ${'focal'} | ${true} + ${'19.10'} | ${'19.10'} | ${true} + ${'jammy'} | ${'jammy-20230816'} | ${false} + ${'jammy-20230816'} | ${'jammy-20230816'} | ${true} + ${'jammy-20230716'} | ${'jammy-20230816'} | ${false} + ${'jammy-20230716.1'} | ${'jammy-20230716.1'} | ${true} + ${'jammy-20230716.1'} | ${'jammy-20230716.2'} | ${false} + ${'jammy-20230716.1'} | ${'jammy-20230816.11'} | ${false} `('equals($a, $b) === $expected', ({ a, b, expected }) => { expect(ubuntu.equals(a, b)).toBe(expected); }); it.each` - a | b | expected - ${'20.04'} | ${'20.10'} | ${false} - ${'20.10'} | ${'20.04'} | ${true} - ${'19.10'} | ${'20.04'} | ${false} - ${'20.04'} | ${'19.10'} | ${true} - ${'16.04'} | ${'16.04.7'} | ${false} - ${'16.04.7'} | ${'16.04'} | ${true} - ${'16.04.1'} | ${'16.04.7'} | ${false} - ${'16.04.7'} | ${'16.04.1'} | ${true} - ${'19.10.1'} | ${'20.04.1'} | ${false} - ${'20.04.1'} | ${'19.10.1'} | ${true} - ${'xxx'} | ${'yyy'} | ${false} - ${'focal'} | ${'groovy'} | ${false} - ${'groovy'} | ${'focal'} | ${true} - ${'eoan'} | ${'focal'} | ${false} - ${'focal'} | ${'eoan'} | ${true} - ${'vivid'} | ${'saucy'} | ${true} - ${'impish'} | ${'focal'} | ${true} - ${'eoan'} | ${'quantal'} | ${true} - ${'focal'} | ${'lucid'} | ${true} - ${'eoan'} | ${'focal'} | ${false} - ${'focal'} | ${'eoan'} | ${true} - ${'jammy'} | ${'focal'} | ${true} - ${'jammy-20230816'} | ${'focal'} | ${true} - ${'jammy-20230816'} | ${'jammy-20230716'} | ${true} - ${'jammy-20230716'} | ${'jammy-20230816'} | ${false} - ${'focal-20230816'} | ${'jammy-20230716'} | ${false} + a | b | expected + ${'20.04'} | ${'20.10'} | ${false} + ${'20.10'} | ${'20.04'} | ${true} + ${'19.10'} | ${'20.04'} | ${false} + ${'20.04'} | ${'19.10'} | ${true} + ${'16.04'} | ${'16.04.7'} | ${false} + ${'16.04.7'} | ${'16.04'} | ${true} + ${'16.04.1'} | ${'16.04.7'} | ${false} + ${'16.04.7'} | ${'16.04.1'} | ${true} + ${'19.10.1'} | ${'20.04.1'} | ${false} + ${'20.04.1'} | ${'19.10.1'} | ${true} + ${'xxx'} | ${'yyy'} | ${false} + ${'focal'} | ${'groovy'} | ${false} + ${'groovy'} | ${'focal'} | ${true} + ${'eoan'} | ${'focal'} | ${false} + ${'focal'} | ${'eoan'} | ${true} + ${'vivid'} | ${'saucy'} | ${true} + ${'impish'} | ${'focal'} | ${true} + ${'eoan'} | ${'quantal'} | ${true} + ${'focal'} | ${'lucid'} | ${true} + ${'eoan'} | ${'focal'} | ${false} + ${'focal'} | ${'eoan'} | ${true} + ${'jammy'} | ${'focal'} | ${true} + ${'jammy-20230816'} | ${'focal'} | ${true} + ${'jammy-20230816'} | ${'jammy-20230716'} | ${true} + ${'jammy-20230716'} | ${'jammy-20230816'} | ${false} + ${'focal-20230816'} | ${'jammy-20230716'} | ${false} + ${'zesty-20170517.1'} | ${'jammy-20240627.1'} | ${false} + ${'jammy-20240627.3'} | ${'jammy-20240627.1'} | ${true} + ${'jammy-20240627.3'} | ${'jammy-20240627.4'} | ${false} + ${'jammy-20240627.1'} | ${'precise-20150228.11'} | ${true} + ${'jammy-20240627'} | ${'precise-20150228.11'} | ${true} `('isGreaterThan("$a", "$b") === $expected', ({ a, b, expected }) => { expect(ubuntu.isGreaterThan(a, b)).toBe(expected); }); diff --git a/lib/modules/versioning/ubuntu/index.ts b/lib/modules/versioning/ubuntu/index.ts index 338b419ecb18ca..ce41ab1a3a0d80 100644 --- a/lib/modules/versioning/ubuntu/index.ts +++ b/lib/modules/versioning/ubuntu/index.ts @@ -4,6 +4,7 @@ import { DistroInfo } from '../distro'; import type { NewValueConfig, VersioningApi } from '../types'; import { getDatedContainerImageCodename, + getDatedContainerImageSuffix, getDatedContainerImageVersion, isDatedCodeName, } from './common'; @@ -105,6 +106,12 @@ function equals(version: string, other: string): boolean { return false; } + const verSuffix = getDatedContainerImageSuffix(version); + const otherSuffix = getDatedContainerImageSuffix(other); + if (verSuffix !== otherSuffix) { + return false; + } + const ver = getVersionByCodename(version); const otherVer = getVersionByCodename(other); return isVersion(ver) && isVersion(otherVer) && ver === otherVer; @@ -138,6 +145,15 @@ function isGreaterThan(version: string, other: string): boolean { return false; } + const xSuffixVersion = getDatedContainerImageSuffix(version) ?? 0; + const ySuffixVersion = getDatedContainerImageSuffix(other) ?? 0; + if (xSuffixVersion > ySuffixVersion) { + return true; + } + if (xSuffixVersion < ySuffixVersion) { + return false; + } + const xPatch = getPatch(version) ?? 0; const yPatch = getPatch(other) ?? 0; return xPatch > yPatch; diff --git a/lib/modules/versioning/unity3d/index.ts b/lib/modules/versioning/unity3d/index.ts index 7d64e8b088ce6f..59e76fcac3d0f0 100644 --- a/lib/modules/versioning/unity3d/index.ts +++ b/lib/modules/versioning/unity3d/index.ts @@ -6,7 +6,7 @@ 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', + 'https://docs.unity3d.com/Manual/assembly-definition-includes.html#version-define-expressions', ]; export const supportsRanges = false; diff --git a/lib/modules/versioning/versioning-metadata.spec.ts b/lib/modules/versioning/versioning-metadata.spec.ts index 2e66857240749c..e7cd9705ecc383 100644 --- a/lib/modules/versioning/versioning-metadata.spec.ts +++ b/lib/modules/versioning/versioning-metadata.spec.ts @@ -1,42 +1,51 @@ -import { readFile, readdir } from 'fs-extra'; +import { readdirSync } from 'node:fs'; +import { readFile } from 'fs-extra'; describe('modules/versioning/versioning-metadata', () => { - it('readme no markdown headers', async () => { - const allVersioning = (await readdir('lib/modules/versioning')).filter( - (item) => !item.includes('.'), - ); - for (const versioning of allVersioning) { - let readme: string | undefined; + const allVersioning = readdirSync('lib/modules/versioning', { + withFileTypes: true, + }) + .filter((item) => item.isDirectory()) + .map((item) => item.name); + + describe.each(allVersioning)('%s', (versioning) => { + it('readme with no h1 or h2 markdown headers', async () => { + let readme = ''; try { readme = await readFile( - 'lib/modules/versioning/' + versioning + '/readme.md', + `lib/modules/versioning/${versioning}/readme.md`, 'utf8', ); } catch { // ignore missing file } - if (readme) { - // eslint-disable-next-line jest/no-conditional-expect - expect(RegExp(/(^|\n)#+ /).exec(readme)).toBeNull(); + const lines = readme.split('\n'); + let isCode = false; + const res: string[] = []; + + for (const line of lines) { + if (line.startsWith('```')) { + isCode = !isCode; + } else if (!isCode) { + res.push(line); + } } - } - }); - it('contains mandatory fields', async () => { - const allVersioning = (await readdir('lib/modules/versioning')).filter( - (item) => !item.includes('.') && !item.startsWith('_'), - ); + expect( + res.some((line) => line.startsWith('# ') || line.startsWith('## ')), + ).toBeFalse(); + }); - for (const versioning of allVersioning) { + it('contains mandatory fields', async () => { const versioningObj = await import(`./${versioning}`); expect(versioningObj.id).toEqual(versioning); expect(versioningObj.displayName).toBeDefined(); - expect(versioningObj.urls).toBeDefined(); - expect(versioningObj.supportsRanges).toBeDefined(); + expect(versioningObj.urls).toBeArray(); + expect(versioningObj.supportsRanges).toBeBoolean(); if (versioningObj.supportsRanges === true) { // eslint-disable-next-line jest/no-conditional-expect - expect(versioningObj.supportedRangeStrategies).toBeDefined(); + expect(versioningObj.supportedRangeStrategies).toBeArrayOfStrings(); } - } + }); }); }); diff --git a/lib/util/cache/package/decorator.ts b/lib/util/cache/package/decorator.ts index 70be6ff6d00d76..ee7424ffe6b288 100644 --- a/lib/util/cache/package/decorator.ts +++ b/lib/util/cache/package/decorator.ts @@ -5,6 +5,7 @@ import { logger } from '../../../logger'; import type { Decorator } from '../../decorator'; import { decorate } from '../../decorator'; import { acquireLock } from '../../mutex'; +import { resolveTtlValues } from './ttl'; import type { DecoratorCachedRecord, PackageCacheNamespace } from './types'; import * as packageCache from '.'; @@ -91,17 +92,13 @@ export function cache({ finalKey, ); - const ttlOverride = getTtlOverride(finalNamespace); - const softTtl = ttlOverride ?? ttlMinutes; - - const cacheHardTtlMinutes = GlobalConfig.get( - 'cacheHardTtlMinutes', - 7 * 24 * 60, - ); - let hardTtl = softTtl; - if (methodName === 'getReleases' || methodName === 'getDigest') { - hardTtl = Math.max(softTtl, cacheHardTtlMinutes); - } + const ttlValues = resolveTtlValues(finalNamespace, ttlMinutes); + const softTtl = ttlValues.softTtlMinutes; + const hardTtl = + methodName === 'getReleases' || methodName === 'getDigest' + ? ttlValues.hardTtlMinutes + : // Skip two-tier TTL for any intermediate data fetching + softTtl; let oldData: unknown; if (oldRecord) { @@ -148,11 +145,3 @@ export function cache({ } }); } - -export function getTtlOverride(namespace: string): number | undefined { - const ttl: unknown = GlobalConfig.get('cacheTtlOverride', {})[namespace]; - if (is.number(ttl)) { - return ttl; - } - return undefined; -} diff --git a/lib/util/cache/package/ttl.ts b/lib/util/cache/package/ttl.ts new file mode 100644 index 00000000000000..5ed8f0b523ea60 --- /dev/null +++ b/lib/util/cache/package/ttl.ts @@ -0,0 +1,43 @@ +import is from '@sindresorhus/is'; +import { GlobalConfig } from '../../../config/global'; +import type { PackageCacheNamespace } from './types'; + +export function getTtlOverride( + namespace: PackageCacheNamespace, +): number | undefined { + const ttl = GlobalConfig.get('cacheTtlOverride', {})[namespace]; + if (is.number(ttl)) { + return ttl; + } + return undefined; +} + +export interface TTLValues { + /** TTL for serving cached value without hitting the server */ + softTtlMinutes: number; + + /** TTL for serving stale cache when upstream responds with errors */ + hardTtlMinutes: number; +} + +/** + * Apply user-configured overrides and return the final values for soft/hard TTL. + * + * @param namespace Cache namespace + * @param ttlMinutes TTL value configured in Renovate codebase + * @returns + */ +export function resolveTtlValues( + namespace: PackageCacheNamespace, + ttlMinutes: number, +): TTLValues { + const softTtlMinutes = getTtlOverride(namespace) ?? ttlMinutes; + + const cacheHardTtlMinutes = GlobalConfig.get( + 'cacheHardTtlMinutes', + 7 * 24 * 60, + ); + const hardTtlMinutes = Math.max(softTtlMinutes, cacheHardTtlMinutes); + + return { softTtlMinutes, hardTtlMinutes }; +} diff --git a/lib/util/cache/package/types.ts b/lib/util/cache/package/types.ts index d61cafdfe6f32e..3f5ed2659061b5 100644 --- a/lib/util/cache/package/types.ts +++ b/lib/util/cache/package/types.ts @@ -34,6 +34,7 @@ export type PackageCacheNamespace = | 'datasource-bazel' | 'datasource-bitbucket-tags' | 'datasource-bitrise' + | 'datasource-buildpacks-registry' | 'datasource-cdnjs' | 'datasource-conan' | 'datasource-conda' diff --git a/lib/util/cache/repository/impl/s3.spec.ts b/lib/util/cache/repository/impl/s3.spec.ts index 8d2d102fbf5b14..f461b5e6ee2de1 100644 --- a/lib/util/cache/repository/impl/s3.spec.ts +++ b/lib/util/cache/repository/impl/s3.spec.ts @@ -129,7 +129,8 @@ describe('util/cache/repository/impl/s3', () => { s3Mock.on(GetObjectCommand, getObjectCommandInput).resolvesOnce({}); await expect(s3Cache.read()).resolves.toBeNull(); expect(logger.warn).toHaveBeenCalledWith( - "RepoCacheS3.read() - failure - expecting Readable return type got 'undefined' type instead", + { returnType: 'undefined' }, + 'RepoCacheS3.read() - failure - got unexpected return type', ); }); diff --git a/lib/util/cache/repository/impl/s3.ts b/lib/util/cache/repository/impl/s3.ts index 42bf18d134e932..a068066ff55841 100644 --- a/lib/util/cache/repository/impl/s3.ts +++ b/lib/util/cache/repository/impl/s3.ts @@ -41,7 +41,8 @@ export class RepoCacheS3 extends RepoCacheBase { return await streamToString(res); } logger.warn( - `RepoCacheS3.read() - failure - expecting Readable return type got '${typeof res}' type instead`, + { returnType: typeof res }, + 'RepoCacheS3.read() - failure - got unexpected return type', ); } catch (err) { // https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html diff --git a/lib/util/git/auth.ts b/lib/util/git/auth.ts index ad2726797ba201..7eefd20c6a6495 100644 --- a/lib/util/git/auth.ts +++ b/lib/util/git/auth.ts @@ -26,8 +26,8 @@ export function getGitAuthenticatedEnvironmentVariables( ): NodeJS.ProcessEnv { if (!token && !(username && password)) { logger.warn( - // TODO: types (#22198) - `Could not create environment variable for ${matchHost!} as neither token or username and password was set`, + { host: matchHost }, + `Could not create environment variable for host as neither token or username and password was set`, ); return { ...environmentVariables }; } @@ -41,9 +41,10 @@ export function getGitAuthenticatedEnvironmentVariables( gitConfigCount = parseInt(gitConfigCountEnvVariable, 10); if (Number.isNaN(gitConfigCount)) { logger.warn( - `Found GIT_CONFIG_COUNT env variable, but couldn't parse the value to an integer: ${String( - process.env.GIT_CONFIG_COUNT, - )}. Ignoring it.`, + { + GIT_CONFIG_COUNT: process.env.GIT_CONFIG_COUNT, + }, + `Found GIT_CONFIG_COUNT env variable, but couldn't parse the value to an integer. Ignoring it.`, ); gitConfigCount = 0; } diff --git a/lib/util/git/index.ts b/lib/util/git/index.ts index 475fb958b908dc..cabe042fb38fe0 100644 --- a/lib/util/git/index.ts +++ b/lib/util/git/index.ts @@ -369,10 +369,7 @@ export async function cloneSubmodules( git.submoduleUpdate(['--init', '--recursive', submodule]), ); } catch (err) { - logger.warn( - { err }, - `Unable to initialise git submodule at ${submodule}`, - ); + logger.warn({ err, submodule }, `Unable to initialise git submodule`); } } } diff --git a/lib/util/github/graphql/cache-strategies/memory-cache-strategy.spec.ts b/lib/util/github/graphql/cache-strategies/memory-cache-strategy.spec.ts index ac1abbb09b38fa..8fb5f1849ae9ac 100644 --- a/lib/util/github/graphql/cache-strategies/memory-cache-strategy.spec.ts +++ b/lib/util/github/graphql/cache-strategies/memory-cache-strategy.spec.ts @@ -1,6 +1,7 @@ import { DateTime, Settings } from 'luxon'; import * as memCache from '../../../cache/memory'; import { clone } from '../../../clone'; +import type { Timestamp } from '../../../timestamp'; import type { GithubDatasourceItem, GithubGraphqlCacheRecord } from '../types'; import { GithubGraphqlMemoryCacheStrategy } from './memory-cache-strategy'; @@ -21,7 +22,7 @@ const isoTs = (t: string) => { } else { throw new Error('Unrecognized date-time string. ' + t); } - return iso; + return iso as Timestamp; }; const mockTime = (input: string): void => { diff --git a/lib/util/github/graphql/cache-strategies/package-cache-strategy.spec.ts b/lib/util/github/graphql/cache-strategies/package-cache-strategy.spec.ts index eb386ca9e6f21d..4208ff1bb5ac1f 100644 --- a/lib/util/github/graphql/cache-strategies/package-cache-strategy.spec.ts +++ b/lib/util/github/graphql/cache-strategies/package-cache-strategy.spec.ts @@ -1,10 +1,11 @@ import { DateTime, Settings } from 'luxon'; +import type { Timestamp } from '../../../../util/timestamp'; import * as packageCache from '../../../cache/package'; import { clone } from '../../../clone'; import type { GithubDatasourceItem, GithubGraphqlCacheRecord } from '../types'; import { GithubGraphqlPackageCacheStrategy } from './package-cache-strategy'; -const isoTs = (t: string) => t.replace(' ', 'T') + ':00.000Z'; +const isoTs = (t: string) => (t.replace(' ', 'T') + ':00.000Z') as Timestamp; const mockTime = (input: string): void => { const now = DateTime.fromISO(isoTs(input)).valueOf(); diff --git a/lib/util/github/graphql/datasource-fetcher.spec.ts b/lib/util/github/graphql/datasource-fetcher.spec.ts index ba139bf80a4c33..7046905135fbef 100644 --- a/lib/util/github/graphql/datasource-fetcher.spec.ts +++ b/lib/util/github/graphql/datasource-fetcher.spec.ts @@ -2,6 +2,7 @@ import AggregateError from 'aggregate-error'; import * as httpMock from '../../../../test/http-mock'; import { mocked, partial } from '../../../../test/util'; import * as _packageCache from '../../../util/cache/package'; +import type { Timestamp } from '../../../util/timestamp'; import type { GithubGraphqlResponse } from '../../http/github'; import { GithubHttp } from '../../http/github'; import { range } from '../../range'; @@ -54,7 +55,7 @@ const adapter: GithubGraphqlDatasourceAdapter< version && releaseTimestamp && foo ? { version, - releaseTimestamp, + releaseTimestamp: releaseTimestamp as Timestamp, bar: foo, } : null, diff --git a/lib/util/github/graphql/index.spec.ts b/lib/util/github/graphql/index.spec.ts index a234bac6dd19c0..0a01cfebd03c4c 100644 --- a/lib/util/github/graphql/index.spec.ts +++ b/lib/util/github/graphql/index.spec.ts @@ -72,7 +72,7 @@ describe('util/github/graphql/index', () => { expect(res).toEqual([ { version: '1.2.3', - releaseTimestamp: '2024-09-24', + releaseTimestamp: '2024-09-24T00:00:00.000Z', url: 'https://example.com', id: 123, name: 'name', diff --git a/lib/util/github/graphql/query-adapters/releases-query-adapter.spec.ts b/lib/util/github/graphql/query-adapters/releases-query-adapter.spec.ts index 7e874c311eff30..bd1620fe2cc5e5 100644 --- a/lib/util/github/graphql/query-adapters/releases-query-adapter.spec.ts +++ b/lib/util/github/graphql/query-adapters/releases-query-adapter.spec.ts @@ -1,9 +1,10 @@ +import type { Timestamp } from '../../../../util/timestamp'; import { adapter } from './releases-query-adapter'; import type { GithubGraphqlRelease } from './releases-query-adapter'; const item: GithubGraphqlRelease = { version: '1.2.3', - releaseTimestamp: '2024-09-24', + releaseTimestamp: '2024-09-24' as Timestamp, isDraft: false, isPrerelease: false, url: 'https://example.com', @@ -18,7 +19,7 @@ describe('util/github/graphql/query-adapters/releases-query-adapter', () => { description: 'description', id: 123, name: 'name', - releaseTimestamp: '2024-09-24', + releaseTimestamp: '2024-09-24T00:00:00.000Z' as Timestamp, url: 'https://example.com', version: '1.2.3', }); diff --git a/lib/util/github/graphql/query-adapters/releases-query-adapter.ts b/lib/util/github/graphql/query-adapters/releases-query-adapter.ts index 65cf0cfb10a2d5..7fe39e8bf908e1 100644 --- a/lib/util/github/graphql/query-adapters/releases-query-adapter.ts +++ b/lib/util/github/graphql/query-adapters/releases-query-adapter.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { Timestamp } from '../../../timestamp'; import type { GithubGraphqlDatasourceAdapter, GithubReleaseItem, @@ -32,7 +33,7 @@ const query = prepareQuery(` const GithubGraphqlRelease = z.object({ version: z.string(), - releaseTimestamp: z.string(), + releaseTimestamp: Timestamp, isDraft: z.boolean(), isPrerelease: z.boolean(), url: z.string(), diff --git a/lib/util/github/graphql/query-adapters/tags-query-adapter.spec.ts b/lib/util/github/graphql/query-adapters/tags-query-adapter.spec.ts index 07a83d1536feb3..ac6f68968f3253 100644 --- a/lib/util/github/graphql/query-adapters/tags-query-adapter.spec.ts +++ b/lib/util/github/graphql/query-adapters/tags-query-adapter.spec.ts @@ -1,3 +1,4 @@ +import type { Timestamp } from '../../../../util/timestamp'; import { adapter } from './tags-query-adapter'; describe('util/github/graphql/query-adapters/tags-query-adapter', () => { @@ -8,14 +9,14 @@ describe('util/github/graphql/query-adapters/tags-query-adapter', () => { target: { type: 'Commit', oid: 'abc123', - releaseTimestamp: '2022-09-24', + releaseTimestamp: '2022-09-24' as Timestamp, }, }), ).toEqual({ version: '1.2.3', gitRef: '1.2.3', hash: 'abc123', - releaseTimestamp: '2022-09-24', + releaseTimestamp: '2022-09-24' as Timestamp, }); }); @@ -26,14 +27,14 @@ describe('util/github/graphql/query-adapters/tags-query-adapter', () => { target: { type: 'Tag', target: { oid: 'abc123' }, - tagger: { releaseTimestamp: '2022-09-24' }, + tagger: { releaseTimestamp: '2022-09-24' as Timestamp }, }, }), ).toEqual({ version: '1.2.3', gitRef: '1.2.3', hash: 'abc123', - releaseTimestamp: '2022-09-24', + releaseTimestamp: '2022-09-24' as Timestamp, }); }); diff --git a/lib/util/github/graphql/query-adapters/tags-query-adapter.ts b/lib/util/github/graphql/query-adapters/tags-query-adapter.ts index 0fbb2cf7728ccc..bdc847b55d92c6 100644 --- a/lib/util/github/graphql/query-adapters/tags-query-adapter.ts +++ b/lib/util/github/graphql/query-adapters/tags-query-adapter.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { Timestamp } from '../../../timestamp'; import type { GithubGraphqlDatasourceAdapter, GithubTagItem } from '../types'; import { prepareQuery } from '../util'; @@ -10,7 +11,7 @@ const GithubGraphqlTag = z.object({ z.object({ type: z.literal('Commit'), oid: z.string(), - releaseTimestamp: z.string(), + releaseTimestamp: Timestamp, }), z.object({ type: z.literal('Tag'), @@ -18,7 +19,7 @@ const GithubGraphqlTag = z.object({ oid: z.string(), }), tagger: z.object({ - releaseTimestamp: z.string(), + releaseTimestamp: Timestamp, }), }), ]), diff --git a/lib/util/github/graphql/types.ts b/lib/util/github/graphql/types.ts index 61c8f864300786..ed0f8bd51fd36e 100644 --- a/lib/util/github/graphql/types.ts +++ b/lib/util/github/graphql/types.ts @@ -1,8 +1,9 @@ import type { PackageCacheNamespace } from '../../cache/package/types'; +import type { Timestamp } from '../../timestamp'; export interface GithubDatasourceItem { version: string; - releaseTimestamp: string; + releaseTimestamp: Timestamp; } /** diff --git a/lib/util/github/tags.spec.ts b/lib/util/github/tags.spec.ts index 5eb4a304404ed1..ff811d09eaea7f 100644 --- a/lib/util/github/tags.spec.ts +++ b/lib/util/github/tags.spec.ts @@ -1,3 +1,4 @@ +import type { Timestamp } from '../../util/timestamp'; import { GithubHttp } from '../http/github'; import * as githubGraphql from './graphql'; import { findCommitOfTag } from './tags'; @@ -12,13 +13,13 @@ describe('util/github/tags', () => { { version: 'v1.0.0', gitRef: 'v1.0.0', - releaseTimestamp: '2021-01-01', + releaseTimestamp: '2021-01-01' as Timestamp, hash: '123', }, { version: 'v2.0.0', gitRef: 'v2.0.0', - releaseTimestamp: '2022-01-01', + releaseTimestamp: '2022-01-01' as Timestamp, hash: 'abc', }, ]); diff --git a/lib/util/http/bitbucket.spec.ts b/lib/util/http/bitbucket.spec.ts index 52cc342b7d5761..fbf3a4ee429de8 100644 --- a/lib/util/http/bitbucket.spec.ts +++ b/lib/util/http/bitbucket.spec.ts @@ -78,7 +78,9 @@ describe('util/http/bitbucket', () => { values: valuesPageThree, page: '3', }); - const res = await api.getJson('/some-url?foo=bar', { paginate: true }); + const res = await api.getJsonUnchecked('/some-url?foo=bar', { + paginate: true, + }); expect(res.body).toEqual({ page: '1', pagelen: 210, @@ -112,7 +114,9 @@ describe('util/http/bitbucket', () => { values: valuesPageThree, page: '3', }); - const res = await api.getJson('some-url?pagelen=10', { paginate: true }); + const res = await api.getJsonUnchecked('some-url?pagelen=10', { + paginate: true, + }); expect(res.body).toEqual({ page: '1', pagelen: 21, @@ -146,7 +150,7 @@ describe('util/http/bitbucket', () => { values: valuesPageThree, page: '3', }); - const res = await api.getJson('some-url', { + const res = await api.getJsonUnchecked('some-url', { paginate: true, pagelen: 20, }); diff --git a/lib/util/http/cache/abstract-http-cache-provider.ts b/lib/util/http/cache/abstract-http-cache-provider.ts index 938e60b836dca6..46ab61e258466d 100644 --- a/lib/util/http/cache/abstract-http-cache-provider.ts +++ b/lib/util/http/cache/abstract-http-cache-provider.ts @@ -2,8 +2,8 @@ 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'; +import { type HttpCache, HttpCacheSchema } from './schema'; +import type { HttpCacheProvider } from './types'; export abstract class AbstractHttpCacheProvider implements HttpCacheProvider { protected abstract load(url: string): Promise; @@ -39,7 +39,14 @@ export abstract class AbstractHttpCacheProvider implements HttpCacheProvider { } } - async wrapResponse( + bypassServer( + _url: string, + _ignoreSoftTtl: boolean, + ): Promise | null> { + return Promise.resolve(null); + } + + async wrapServerResponse( url: string, resp: HttpResponse, ): Promise> { diff --git a/lib/util/http/cache/package-http-cache-provider.spec.ts b/lib/util/http/cache/package-http-cache-provider.spec.ts new file mode 100644 index 00000000000000..e5951b2422c104 --- /dev/null +++ b/lib/util/http/cache/package-http-cache-provider.spec.ts @@ -0,0 +1,131 @@ +import { mockDeep } from 'jest-mock-extended'; +import { DateTime, Settings } from 'luxon'; +import { Http } from '..'; +import * as httpMock from '../../../../test/http-mock'; +import { mocked } from '../../../../test/util'; +import * as _packageCache from '../../cache/package'; +import { PackageHttpCacheProvider } from './package-http-cache-provider'; +import type { HttpCache } from './schema'; + +jest.mock('../../../util/cache/package', () => mockDeep()); +const packageCache = mocked(_packageCache); + +const http = new Http('test'); + +describe('util/http/cache/package-http-cache-provider', () => { + const url = 'http://example.com/foo/bar'; + + let cache: Record = {}; + + beforeEach(() => { + jest.resetAllMocks(); + cache = {}; + + packageCache.get.mockImplementation((_ns, k) => { + const res = cache[k] as never; + return Promise.resolve(res); + }); + + packageCache.set.mockImplementation((_ns, k, v, _ttl) => { + cache[k] = v as HttpCache; + return Promise.resolve(null as never); + }); + }); + + const mockTime = (time: string) => { + const value = DateTime.fromISO(time).valueOf(); + Settings.now = () => value; + }; + + beforeAll(() => {}); + + it('loads cache correctly', async () => { + mockTime('2024-06-15T00:00:00.000Z'); + + cache[url] = { + etag: 'etag-value', + lastModified: 'Fri, 15 Jun 2024 00:00:00 GMT', + httpResponse: { statusCode: 200, body: 'old response' }, + timestamp: '2024-06-15T00:00:00.000Z', + }; + const cacheProvider = new PackageHttpCacheProvider({ + namespace: '_test-namespace', + ttlMinutes: 0, + }); + httpMock.scope(url).get('').reply(200, 'new response'); + + const res = await http.get(url, { cacheProvider }); + + expect(res.body).toBe('new response'); + }); + + it('loads cache bypassing server', async () => { + mockTime('2024-06-15T00:14:59.999Z'); + cache[url] = { + etag: 'etag-value', + lastModified: 'Fri, 15 Jun 2024 00:00:00 GMT', + httpResponse: { statusCode: 200, body: 'cached response' }, + timestamp: '2024-06-15T00:00:00.000Z', + }; + const cacheProvider = new PackageHttpCacheProvider({ + namespace: '_test-namespace', + }); + + const res = await http.get(url, { cacheProvider }); + + expect(res.body).toBe('cached response'); + expect(packageCache.set).not.toHaveBeenCalled(); + + mockTime('2024-06-15T00:15:00.000Z'); + httpMock.scope(url).get('').reply(200, 'new response', { etag: 'foobar' }); + + const res2 = await http.get(url, { cacheProvider }); + expect(res2.body).toBe('new response'); + expect(packageCache.set).toHaveBeenCalled(); + }); + + it('handles cache miss', async () => { + const cacheProvider = new PackageHttpCacheProvider({ + namespace: '_test-namespace', + }); + httpMock + .scope(url) + .get('') + .reply(200, 'fetched response', { etag: 'foobar' }); + + const res = await http.get(url, { cacheProvider }); + + expect(res.body).toBe('fetched response'); + expect(cache).toEqual({ + 'http://example.com/foo/bar': { + etag: 'foobar', + httpResponse: { + statusCode: 200, + headers: expect.any(Object), + body: 'fetched response', + }, + lastModified: undefined, + timestamp: expect.any(String), + }, + }); + }); + + it('serves stale response during revalidation error', async () => { + mockTime('2024-06-15T00:15:00.000Z'); + cache[url] = { + etag: 'etag-value', + lastModified: 'Fri, 15 Jun 2024 00:00:00 GMT', + httpResponse: { statusCode: 200, body: 'cached response' }, + timestamp: '2024-06-15T00:00:00.000Z', + }; + const cacheProvider = new PackageHttpCacheProvider({ + namespace: '_test-namespace', + }); + httpMock.scope(url).get('').reply(500); + + const res = await http.get(url, { cacheProvider }); + + expect(res.body).toBe('cached response'); + expect(packageCache.set).not.toHaveBeenCalled(); + }); +}); diff --git a/lib/util/http/cache/package-http-cache-provider.ts b/lib/util/http/cache/package-http-cache-provider.ts new file mode 100644 index 00000000000000..0aa1607ea59825 --- /dev/null +++ b/lib/util/http/cache/package-http-cache-provider.ts @@ -0,0 +1,64 @@ +import { DateTime } from 'luxon'; +import { get, set } from '../../cache/package'; // Import the package cache functions +import { resolveTtlValues } from '../../cache/package/ttl'; +import type { PackageCacheNamespace } from '../../cache/package/types'; +import { HttpCacheStats } from '../../stats'; +import type { HttpResponse } from '../types'; +import { AbstractHttpCacheProvider } from './abstract-http-cache-provider'; +import type { HttpCache } from './schema'; + +export interface PackageHttpCacheProviderOptions { + namespace: PackageCacheNamespace; + ttlMinutes?: number; +} + +export class PackageHttpCacheProvider extends AbstractHttpCacheProvider { + private namespace: PackageCacheNamespace; + + private softTtlMinutes: number; + private hardTtlMinutes: number; + + constructor({ namespace, ttlMinutes = 15 }: PackageHttpCacheProviderOptions) { + super(); + this.namespace = namespace; + const { softTtlMinutes, hardTtlMinutes } = resolveTtlValues( + this.namespace, + ttlMinutes, + ); + this.softTtlMinutes = softTtlMinutes; + this.hardTtlMinutes = hardTtlMinutes; + } + + async load(url: string): Promise { + return await get(this.namespace, url); + } + + async persist(url: string, data: HttpCache): Promise { + await set(this.namespace, url, data, this.hardTtlMinutes); + } + + override async bypassServer( + url: string, + ignoreSoftTtl = false, + ): Promise | null> { + const cached = await this.get(url); + if (!cached) { + return null; + } + + if (ignoreSoftTtl) { + return cached.httpResponse as HttpResponse; + } + + const cachedAt = DateTime.fromISO(cached.timestamp); + const deadline = cachedAt.plus({ minutes: this.softTtlMinutes }); + const now = DateTime.now(); + if (now >= deadline) { + HttpCacheStats.incLocalMisses(url); + return null; + } + + HttpCacheStats.incLocalHits(url); + return cached.httpResponse as HttpResponse; + } +} diff --git a/lib/util/http/cache/repository-http-cache-provider.spec.ts b/lib/util/http/cache/repository-http-cache-provider.spec.ts index a8acbba4b11786..5b070114cee82d 100644 --- a/lib/util/http/cache/repository-http-cache-provider.spec.ts +++ b/lib/util/http/cache/repository-http-cache-provider.spec.ts @@ -1,7 +1,7 @@ import { Http } from '..'; import * as httpMock from '../../../../test/http-mock'; import { logger } from '../../../../test/util'; -import { getCache, resetCache } from '../../cache/repository'; +import { resetCache } from '../../cache/repository'; import { repoCacheProvider } from './repository-http-cache-provider'; describe('util/http/cache/repository-http-cache-provider', () => { @@ -17,7 +17,7 @@ describe('util/http/cache/repository-http-cache-provider', () => { 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'); + const res1 = await http.getJsonUnchecked('https://example.com/foo/bar'); expect(res1).toMatchObject({ statusCode: 200, body: { msg: 'Hello, world!' }, @@ -25,7 +25,7 @@ describe('util/http/cache/repository-http-cache-provider', () => { }); scope.get('/foo/bar').reply(304); - const res2 = await http.getJson('https://example.com/foo/bar'); + const res2 = await http.getJsonUnchecked('https://example.com/foo/bar'); expect(res2).toMatchObject({ statusCode: 200, body: { msg: 'Hello, world!' }, @@ -43,7 +43,7 @@ describe('util/http/cache/repository-http-cache-provider', () => { { msg: 'Hello, world!' }, { 'last-modified': 'Mon, 01 Jan 2000 00:00:00 GMT' }, ); - const res1 = await http.getJson('https://example.com/foo/bar'); + const res1 = await http.getJsonUnchecked('https://example.com/foo/bar'); expect(res1).toMatchObject({ statusCode: 200, body: { msg: 'Hello, world!' }, @@ -51,7 +51,7 @@ describe('util/http/cache/repository-http-cache-provider', () => { }); scope.get('/foo/bar').reply(304); - const res2 = await http.getJson('https://example.com/foo/bar'); + const res2 = await http.getJsonUnchecked('https://example.com/foo/bar'); expect(res2).toMatchObject({ statusCode: 200, body: { msg: 'Hello, world!' }, @@ -59,34 +59,13 @@ describe('util/http/cache/repository-http-cache-provider', () => { }); }); - 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'); + await http.getJsonUnchecked('https://example.com/foo/bar'); expect(logger.logger.debug).toHaveBeenCalledWith( 'http cache: failed to persist cache for https://example.com/foo/bar', @@ -97,7 +76,7 @@ describe('util/http/cache/repository-http-cache-provider', () => { 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'); + const res1 = await http.getJsonUnchecked('https://example.com/foo/bar'); expect(res1).toMatchObject({ statusCode: 200, body: { msg: 'Hello, world!' }, @@ -107,7 +86,7 @@ describe('util/http/cache/repository-http-cache-provider', () => { resetCache(); scope.get('/foo/bar').reply(304); - const res2 = await http.getJson('https://example.com/foo/bar'); + const res2 = await http.getJsonUnchecked('https://example.com/foo/bar'); expect(res2).toMatchObject({ statusCode: 304, authorization: false, @@ -118,7 +97,7 @@ describe('util/http/cache/repository-http-cache-provider', () => { const scope = httpMock.scope('https://example.com'); scope.get('/foo/bar').reply(203); - const res = await http.getJson('https://example.com/foo/bar'); + const res = await http.getJsonUnchecked('https://example.com/foo/bar'); expect(res).toMatchObject({ statusCode: 203, @@ -130,7 +109,7 @@ describe('util/http/cache/repository-http-cache-provider', () => { 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', { + const res1 = await http.getJsonUnchecked('https://example.com/foo/bar', { headers: { authorization: 'Bearer 123' }, }); expect(res1).toMatchObject({ @@ -140,7 +119,7 @@ describe('util/http/cache/repository-http-cache-provider', () => { }); scope.get('/foo/bar').reply(304); - const res2 = await http.getJson('https://example.com/foo/bar', { + const res2 = await http.getJsonUnchecked('https://example.com/foo/bar', { headers: { authorization: 'Bearer 123' }, }); expect(res2).toMatchObject({ diff --git a/lib/util/http/cache/repository-http-cache-provider.ts b/lib/util/http/cache/repository-http-cache-provider.ts index 9cf9c8cfa3b0c1..8ec1909d19497a 100644 --- a/lib/util/http/cache/repository-http-cache-provider.ts +++ b/lib/util/http/cache/repository-http-cache-provider.ts @@ -1,6 +1,6 @@ import { getCache } from '../../cache/repository'; import { AbstractHttpCacheProvider } from './abstract-http-cache-provider'; -import type { HttpCache } from './types'; +import type { HttpCache } from './schema'; export class RepositoryHttpCacheProvider extends AbstractHttpCacheProvider { override load(url: string): Promise { diff --git a/lib/util/http/cache/schema.ts b/lib/util/http/cache/schema.ts index d1d71fda9bc61a..c9b9db1e291d92 100644 --- a/lib/util/http/cache/schema.ts +++ b/lib/util/http/cache/schema.ts @@ -1,34 +1,16 @@ 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; + etag: z.string().optional(), + lastModified: z.string().optional(), + httpResponse: z.unknown(), + timestamp: z.string(), }) - .pipe( - z - .object({ - etag: z.string().optional(), - lastModified: z.string().optional(), - httpResponse: z.unknown(), - timestamp: z.string(), - }) - .refine( - ({ etag, lastModified }) => etag ?? lastModified, - invalidFieldsMsg, - ), + .refine( + ({ etag, lastModified }) => etag ?? lastModified, + 'Cache object should have `etag` or `lastModified` fields', ) .nullable() .catch(null); +export type HttpCache = z.infer; diff --git a/lib/util/http/cache/types.ts b/lib/util/http/cache/types.ts index 1159f58028762f..7a763fd6317f68 100644 --- a/lib/util/http/cache/types.ts +++ b/lib/util/http/cache/types.ts @@ -1,17 +1,18 @@ 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>; + bypassServer( + url: string, + ignoreSoftTtl?: boolean, + ): Promise | null>; + + wrapServerResponse( + url: string, + resp: HttpResponse, + ): Promise>; } diff --git a/lib/util/http/gerrit.spec.ts b/lib/util/http/gerrit.spec.ts index 7c99f72d5f4f01..7d6563817cf5a8 100644 --- a/lib/util/http/gerrit.spec.ts +++ b/lib/util/http/gerrit.spec.ts @@ -33,7 +33,7 @@ describe('util/http/gerrit', () => { }); const res = await api - .getJson('some-url', { headers: { a: 'b' } }) + .getJsonUnchecked('some-url', { headers: { a: 'b' } }) .then((res) => res.body); return expect(res).toEqual(body); }); diff --git a/lib/util/http/gitea.spec.ts b/lib/util/http/gitea.spec.ts index 666d6f94816155..6342d81027024a 100644 --- a/lib/util/http/gitea.spec.ts +++ b/lib/util/http/gitea.spec.ts @@ -18,7 +18,7 @@ describe('util/http/gitea', () => { .get('/pagination-example-1') .reply(200, { hello: 'world' }); - const res = await giteaHttp.getJson('pagination-example-1', { + const res = await giteaHttp.getJsonUnchecked('pagination-example-1', { paginate: true, }); expect(res.body).toEqual({ hello: 'world' }); @@ -34,9 +34,10 @@ describe('util/http/gitea', () => { .get('/pagination-example-1?page=3') .reply(200, ['mno', 'pqr']); - const res = await giteaHttp.getJson(`${baseUrl}/pagination-example-1`, { - paginate: true, - }); + const res = await giteaHttp.getJsonUnchecked( + `${baseUrl}/pagination-example-1`, + { paginate: true }, + ); expect(res.body).toHaveLength(6); expect(res.body).toEqual(['abc', 'def', 'ghi', 'jkl', 'mno', 'pqr']); @@ -52,7 +53,7 @@ describe('util/http/gitea', () => { .get('/pagination-example-2?page=3') .reply(200, { data: ['mno', 'pqr'] }); - const res = await giteaHttp.getJson<{ data: string[] }>( + const res = await giteaHttp.getJsonUnchecked<{ data: string[] }>( 'pagination-example-2', { paginate: true, @@ -70,7 +71,7 @@ describe('util/http/gitea', () => { .get('/pagination-example-3?page=2') .reply(200, { data: [] }); - const res = await giteaHttp.getJson<{ data: string[] }>( + const res = await giteaHttp.getJsonUnchecked<{ data: string[] }>( 'pagination-example-3', { paginate: true, diff --git a/lib/util/http/github.spec.ts b/lib/util/http/github.spec.ts index 31292e7b3a0487..70dc177f8cf12b 100644 --- a/lib/util/http/github.spec.ts +++ b/lib/util/http/github.spec.ts @@ -107,7 +107,7 @@ describe('util/http/github', () => { }) .get(`${url}&page=3`) .reply(200, ['e']); - const res = await githubApi.getJson(url, { paginate: true }); + const res = await githubApi.getJsonUnchecked(url, { paginate: true }); expect(res.body).toEqual(['a', 'b', 'c', 'd', 'e']); }); @@ -133,7 +133,7 @@ describe('util/http/github', () => { ) .get(`${url}?page=3`) .reply(200, { the_field: ['d'], total: 4 }); - const res: any = await githubApi.getJson('some-url', { + const res = await githubApi.getJsonUnchecked('some-url', { paginate: true, paginationField: 'the_field', }); @@ -169,7 +169,7 @@ describe('util/http/github', () => { }) .get(`${url}&page=3`) .reply(200, ['e']); - const res = await githubApi.getJson(url, { + const res = await githubApi.getJsonUnchecked(url, { paginate: true, repository: 'some/repo', }); @@ -206,7 +206,7 @@ describe('util/http/github', () => { }) .get(`${url}&page=3`) .reply(200, ['e']); - const res = await githubApi.getJson(url, { + const res = await githubApi.getJsonUnchecked(url, { paginate: true, repository: 'some/repo', baseUrl: 'https://github.domain.com', @@ -225,7 +225,9 @@ describe('util/http/github', () => { .reply(200, ['a'], { link: `<${url}?page=34>; rel="last"`, }); - const res = await githubApi.getJson('some-url', { paginate: true }); + const res = await githubApi.getJsonUnchecked('some-url', { + paginate: true, + }); expect(res).toBeDefined(); expect(res.body).toEqual(['a']); }); @@ -253,7 +255,9 @@ describe('util/http/github', () => { }) .get(`${apiUrl}&page=3`) .reply(200, ['e']); - const res = await githubApi.getJson(apiUrl, { paginate: true }); + const res = await githubApi.getJsonUnchecked(apiUrl, { + paginate: true, + }); expect(res.body).toEqual(['a', 'b', 'c', 'd', 'e']); }); @@ -273,7 +277,9 @@ describe('util/http/github', () => { }) .get(`${apiUrl}&page=3`) .reply(200, ['e']); - const res = await githubApi.getJson(apiUrl, { paginate: true }); + const res = await githubApi.getJsonUnchecked(apiUrl, { + paginate: true, + }); expect(res.body).toEqual(['a', 'b', 'c', 'd', 'e']); }); @@ -295,7 +301,9 @@ describe('util/http/github', () => { }) .get(`/${apiUrl}&page=3`) .reply(200, ['e']); - const res = await githubApi.getJson(apiUrl, { paginate: true }); + const res = await githubApi.getJsonUnchecked(apiUrl, { + paginate: true, + }); expect(res.body).toEqual(['a', 'b', 'c', 'd', 'e']); }); @@ -320,13 +328,13 @@ describe('util/http/github', () => { }, headers, ); - await githubApi.getJson(url); + await githubApi.getJsonUnchecked(url); } async function failWithError(error: string | Record) { const url = '/some-url'; httpMock.scope(githubApiHost).get(url).replyWithError(error); - await githubApi.getJson(url); + await githubApi.getJsonUnchecked(url); } it('should throw Not found', async () => { diff --git a/lib/util/http/gitlab.spec.ts b/lib/util/http/gitlab.spec.ts index f3f3363eb64fa6..6ab99079d319a2 100644 --- a/lib/util/http/gitlab.spec.ts +++ b/lib/util/http/gitlab.spec.ts @@ -44,7 +44,9 @@ describe('util/http/gitlab', () => { }) .get('/api/v4/some-url&page=3') .reply(200, ['d']); - const res = await gitlabApi.getJson('some-url', { paginate: true }); + const res = await gitlabApi.getJsonUnchecked('some-url', { + paginate: true, + }); expect(res.body).toHaveLength(4); }); @@ -64,7 +66,9 @@ describe('util/http/gitlab', () => { }) .get('/api/v4/some-url&page=3') .reply(200, ['d']); - const res = await gitlabApi.getJson('some-url', { paginate: true }); + const res = await gitlabApi.getJsonUnchecked('some-url', { + paginate: true, + }); expect(res.body).toHaveLength(4); }); @@ -87,7 +91,9 @@ describe('util/http/gitlab', () => { httpMock.scope(gitlabApiHost).get('/api/v4/some-url').reply(200, ['a'], { link: '; rel="last"', }); - const res = await gitlabApi.getJson('some-url', { paginate: true }); + const res = await gitlabApi.getJsonUnchecked('some-url', { + paginate: true, + }); expect(res.body).toHaveLength(1); }); @@ -140,7 +146,7 @@ describe('util/http/gitlab', () => { it('ParseError', async () => { httpMock.scope(gitlabApiHost).get('/api/v4/some-url').reply(200, '{{'); - await expect(gitlabApi.getJson('some-url')).rejects.toThrow( + await expect(gitlabApi.getJsonUnchecked('some-url')).rejects.toThrow( EXTERNAL_HOST_ERROR, ); }); diff --git a/lib/util/http/host-rules.spec.ts b/lib/util/http/host-rules.spec.ts index dcbaa06de70a4e..3ca3db64fb371e 100644 --- a/lib/util/http/host-rules.spec.ts +++ b/lib/util/http/host-rules.spec.ts @@ -543,4 +543,24 @@ describe('util/http/host-rules', () => { }, }); }); + + it('should replace existing headers with host rule headers', () => { + GlobalConfig.set({ allowedHeaders: ['Accept'] }); + const hostRule = { + matchHost: 'https://domain.com/all-versions', + headers: { + Accept: 'replacement', + }, + }; + const options = { + headers: { + Accept: 'default', + }, + }; + expect(applyHostRule(url, options, hostRule)).toEqual({ + headers: { + Accept: 'replacement', + }, + }); + }); }); diff --git a/lib/util/http/host-rules.ts b/lib/util/http/host-rules.ts index fa575ee8c844c5..b70b19b20f7292 100644 --- a/lib/util/http/host-rules.ts +++ b/lib/util/http/host-rules.ts @@ -176,8 +176,8 @@ export function applyHostRule( } options.headers = { - ...filteredHeaders, ...options.headers, + ...filteredHeaders, }; } diff --git a/lib/util/http/index.spec.ts b/lib/util/http/index.spec.ts index 3c1b37bb755f65..57eafc9007c118 100644 --- a/lib/util/http/index.spec.ts +++ b/lib/util/http/index.spec.ts @@ -80,7 +80,7 @@ describe('util/http/index', () => { .get('/') .reply(200, '{ "test": true }', { etag: 'abc123' }); - const res = await http.getJson('http://renovate.com'); + const res = await http.getJsonUnchecked('http://renovate.com'); expect(res).toEqual({ authorization: false, @@ -342,6 +342,172 @@ describe('util/http/index', () => { memCache.reset(); }); + describe('getPlain', () => { + it('gets plain text with correct headers', async () => { + httpMock.scope(baseUrl).get('/').reply(200, 'plain text response', { + 'content-type': 'text/plain', + }); + + const res = await http.getPlain('http://renovate.com'); + expect(res.body).toBe('plain text response'); + expect(res.headers['content-type']).toBe('text/plain'); + }); + + it('works with custom options', async () => { + httpMock + .scope(baseUrl) + .get('/') + .matchHeader('custom', 'header') + .reply(200, 'plain text response'); + + const res = await http.getPlain('http://renovate.com', { + headers: { custom: 'header' }, + }); + expect(res.body).toBe('plain text response'); + }); + }); + + describe('getYamlUnchecked', () => { + it('parses yaml response without schema', async () => { + httpMock.scope(baseUrl).get('/').reply(200, 'x: 2\ny: 2'); + + const res = await http.getYamlUnchecked('http://renovate.com'); + expect(res.body).toEqual({ x: 2, y: 2 }); + }); + + it('parses yaml with options', async () => { + httpMock + .scope(baseUrl) + .get('/') + .matchHeader('custom', 'header') + .reply(200, 'x: 2\ny: 2'); + + const res = await http.getYamlUnchecked('http://renovate.com', { + headers: { custom: 'header' }, + }); + expect(res.body).toEqual({ x: 2, y: 2 }); + }); + + it('throws on invalid yaml', async () => { + httpMock.scope(baseUrl).get('/').reply(200, '!@#$%^'); + + await expect( + http.getYamlUnchecked('http://renovate.com'), + ).rejects.toThrow('Failed to parse YAML file'); + }); + }); + + describe('getYaml', () => { + it('parses yaml with schema validation', async () => { + httpMock.scope(baseUrl).get('/').reply(200, 'x: 2\ny: 2'); + + const res = await http.getYaml('http://renovate.com', SomeSchema); + expect(res.body).toBe('2 + 2 = 4'); + }); + + it('parses yaml with options and schema', async () => { + httpMock + .scope(baseUrl) + .get('/') + .matchHeader('custom', 'header') + .reply(200, 'x: 2\ny: 2'); + + const res = await http.getYaml( + 'http://renovate.com', + { headers: { custom: 'header' } }, + SomeSchema, + ); + expect(res.body).toBe('2 + 2 = 4'); + }); + + it('throws on schema validation failure', async () => { + httpMock.scope(baseUrl).get('/').reply(200, 'foo: bar'); + + await expect( + http.getYaml('http://renovate.com', SomeSchema), + ).rejects.toThrow(z.ZodError); + }); + + it('throws on invalid yaml', async () => { + httpMock.scope(baseUrl).get('/').reply(200, '!@#$%^'); + + await expect( + http.getYaml('http://renovate.com', SomeSchema), + ).rejects.toThrow('Failed to parse YAML file'); + }); + }); + + describe('getYamlSafe', () => { + it('returns successful result with schema validation', async () => { + httpMock.scope('http://example.com').get('/').reply(200, 'x: 2\ny: 2'); + + const { val, err } = await http + .getYamlSafe('http://example.com', SomeSchema) + .unwrap(); + + expect(val).toBe('2 + 2 = 4'); + expect(err).toBeUndefined(); + }); + + it('returns schema error result', async () => { + httpMock + .scope('http://example.com') + .get('/') + .reply(200, 'x: "2"\ny: "2"'); + + const { val, err } = await http + .getYamlSafe('http://example.com', SomeSchema) + .unwrap(); + + expect(val).toBeUndefined(); + expect(err).toBeInstanceOf(ZodError); + }); + + it('returns error result for invalid yaml', async () => { + httpMock.scope('http://example.com').get('/').reply(200, '!@#$%^'); + + const { val, err } = await http + .getYamlSafe('http://example.com', SomeSchema) + .unwrap(); + + expect(val).toBeUndefined(); + expect(err).toBeDefined(); + }); + + it('returns error result for network errors', async () => { + httpMock + .scope('http://example.com') + .get('/') + .replyWithError('network error'); + + const { val, err } = await http + .getYamlSafe('http://example.com', SomeSchema) + .unwrap(); + + expect(val).toBeUndefined(); + expect(err).toBeInstanceOf(HttpError); + }); + + it('works with options and schema', async () => { + httpMock + .scope('http://example.com') + .get('/') + .matchHeader('custom', 'header') + .reply(200, 'x: 2\ny: 2'); + + const { val, err } = await http + .getYamlSafe( + 'http://example.com', + { headers: { custom: 'header' } }, + SomeSchema, + ) + .unwrap(); + + expect(val).toBe('2 + 2 = 4'); + expect(err).toBeUndefined(); + }); + }); + describe('getJson', () => { it('uses schema for response body', async () => { httpMock diff --git a/lib/util/http/index.ts b/lib/util/http/index.ts index 34ce5a691ecfa4..e05e745cae6a4f 100644 --- a/lib/util/http/index.ts +++ b/lib/util/http/index.ts @@ -15,6 +15,7 @@ import { hash } from '../hash'; import { type AsyncResult, Result } from '../result'; import { type HttpRequestStatsDataPoint, HttpStats } from '../stats'; import { resolveBaseUrl } from '../url'; +import { parseSingleYaml } from '../yaml'; import { applyAuthorization, removeAuthorization } from './auth'; import { hooks } from './hooks'; import { applyHostRule, findMatchingRule } from './host-rules'; @@ -38,7 +39,7 @@ export { RequestError as HttpError }; export class EmptyResultError extends Error {} export type SafeJsonError = RequestError | ZodError | EmptyResultError; -type JsonArgs< +type HttpFnArgs< Opts extends HttpOptions, ResT = unknown, Schema extends ZodType = ZodType, @@ -179,6 +180,12 @@ export class Http { options = applyAuthorization(options); options.timeout ??= 60000; + const { cacheProvider } = options; + const cachedResponse = await cacheProvider?.bypassServer(url); + if (cachedResponse) { + return cachedResponse; + } + const memCacheKey = options.memCache !== false && (options.method === 'get' || options.method === 'head') @@ -200,8 +207,8 @@ export class Http { // istanbul ignore else: no cache tests if (!resPromise) { - if (options.cacheProvider) { - await options.cacheProvider.setCacheHeaders(url, options); + if (cacheProvider) { + await cacheProvider.setCacheHeaders(url, options); } const startTime = Date.now(); @@ -234,8 +241,8 @@ export class Http { const resCopy = copyResponse(res, deepCopyNeeded); resCopy.authorization = !!options?.headers?.authorization; - if (options.cacheProvider) { - return await options.cacheProvider.wrapResponse(url, resCopy); + if (cacheProvider) { + return await cacheProvider.wrapServerResponse(url, resCopy); } return resCopy; @@ -244,6 +251,16 @@ export class Http { if (abortOnError && !abortIgnoreStatusCodes?.includes(err.statusCode)) { throw new ExternalHostError(err); } + + const staleResponse = await cacheProvider?.bypassServer(url, true); + if (staleResponse) { + logger.debug( + { err }, + `Request error: returning stale cache instead for ${url}`, + ); + return staleResponse; + } + throw err; } } @@ -272,7 +289,7 @@ export class Http { private async requestJson( method: InternalHttpOptions['method'], - { url, httpOptions: requestOptions, schema }: JsonArgs, + { url, httpOptions: requestOptions, schema }: HttpFnArgs, ): Promise> { const { body, ...httpOptions } = { ...requestOptions }; const opts: InternalHttpOptions = { @@ -302,8 +319,8 @@ export class Http { arg1: string, arg2: Opts | ZodType | undefined, arg3: ZodType | undefined, - ): JsonArgs { - const res: JsonArgs = { url: arg1 }; + ): HttpFnArgs { + const res: HttpFnArgs = { url: arg1 }; if (arg2 instanceof ZodType) { res.schema = arg2; @@ -328,30 +345,57 @@ export class Http { }); } - getJson(url: string, options?: Opts): Promise>; - getJson = ZodType>( + /** + * @deprecated use `getYaml` instead + */ + async getYamlUnchecked( + url: string, + options?: Opts, + ): Promise> { + const res = await this.get(url, options); + const body = parseSingleYaml(res.body); + return { ...res, body }; + } + + async getYaml>( url: string, schema: Schema, ): Promise>>; - getJson = ZodType>( + async getYaml>( url: string, options: Opts, schema: Schema, ): Promise>>; - getJson = ZodType>( + async getYaml>( arg1: string, arg2?: Opts | Schema, arg3?: Schema, - ): Promise> { - const args = this.resolveArgs(arg1, arg2, arg3); - return this.requestJson('get', args); + ): Promise>> { + const url = arg1; + let schema: Schema; + let httpOptions: Opts | undefined; + if (arg3) { + schema = arg3; + httpOptions = arg2 as Opts; + } else { + schema = arg2 as Schema; + } + + const opts: InternalHttpOptions = { + ...httpOptions, + method: 'get', + }; + + const res = await this.get(url, opts); + const body = await schema.parseAsync(parseSingleYaml(res.body)); + return { ...res, body }; } - getJsonSafe< + getYamlSafe< ResT extends NonNullable, Schema extends ZodType = ZodType, >(url: string, schema: Schema): AsyncResult, SafeJsonError>; - getJsonSafe< + getYamlSafe< ResT extends NonNullable, Schema extends ZodType = ZodType, >( @@ -359,10 +403,93 @@ export class Http { options: Opts, schema: Schema, ): AsyncResult, SafeJsonError>; - getJsonSafe< + getYamlSafe< ResT extends NonNullable, Schema extends ZodType = ZodType, >( + arg1: string, + arg2: Opts | Schema, + arg3?: Schema, + ): AsyncResult { + const url = arg1; + let schema: Schema; + let httpOptions: Opts | undefined; + if (arg3) { + schema = arg3; + httpOptions = arg2 as Opts; + } else { + schema = arg2 as Schema; + } + + let res: AsyncResult, SafeJsonError>; + if (httpOptions) { + res = Result.wrap(this.getYaml(url, httpOptions, schema)); + } else { + res = Result.wrap(this.getYaml(url, schema)); + } + + return res.transform((response) => Result.ok(response.body)); + } + + /** + * Request JSON and return the response without any validation. + * + * The usage of this method is discouraged, please use `getJson` instead. + * + * If you're new to Zod schema validation library: + * - consult the [documentation of Zod library](https://github.com/colinhacks/zod?tab=readme-ov-file#basic-usage) + * - search the Renovate codebase for 'zod' module usage + * - take a look at the `schema-utils.ts` file for Renovate-specific schemas and utilities + */ + getJsonUnchecked( + url: string, + options?: Opts, + ): Promise> { + return this.requestJson('get', { url, httpOptions: options }); + } + + /** + * Request JSON with a Zod schema for the response, + * throwing an error if the response is not valid. + * + * @param url + * @param schema Zod schema for the response + */ + getJson>( + url: string, + schema: Schema, + ): Promise>>; + getJson>( + url: string, + options: Opts, + schema: Schema, + ): Promise>>; + getJson>( + arg1: string, + arg2?: Opts | Schema, + arg3?: Schema, + ): Promise>> { + const args = this.resolveArgs>(arg1, arg2, arg3); + return this.requestJson>('get', args); + } + + /** + * Request JSON with a Zod schema for the response, + * wrapping response data in a `Result` class. + * + * @param url + * @param schema Zod schema for the response + */ + getJsonSafe, Schema extends ZodType>( + url: string, + schema: Schema, + ): AsyncResult, SafeJsonError>; + getJsonSafe, Schema extends ZodType>( + url: string, + options: Opts, + schema: Schema, + ): AsyncResult, SafeJsonError>; + getJsonSafe, Schema extends ZodType>( arg1: string, arg2?: Opts | Schema, arg3?: Schema, diff --git a/lib/util/merge-confidence/index.ts b/lib/util/merge-confidence/index.ts index 93844cd7b33ecb..8491263c61bf98 100644 --- a/lib/util/merge-confidence/index.ts +++ b/lib/util/merge-confidence/index.ts @@ -168,7 +168,9 @@ async function queryApi( let confidence: MergeConfidence = 'neutral'; try { - const res = (await http.getJson<{ confidence: MergeConfidence }>(url)).body; + const res = ( + await http.getJsonUnchecked<{ confidence: MergeConfidence }>(url) + ).body; if (isMergeConfidence(res.confidence)) { confidence = res.confidence; } diff --git a/lib/util/pretty-time.spec.ts b/lib/util/pretty-time.spec.ts index 693e73c4fa8e6f..4a1017f1c10375 100644 --- a/lib/util/pretty-time.spec.ts +++ b/lib/util/pretty-time.spec.ts @@ -47,7 +47,7 @@ describe('util/pretty-time', () => { }); describe('satisfiesDateRange()', () => { - const t0 = DateTime.fromISO('2023-07-07T12:00:00'); + const t0 = DateTime.fromISO('2023-07-07T12:00:00Z'); beforeAll(() => { jest.useFakeTimers(); diff --git a/lib/util/result.ts b/lib/util/result.ts index 86f48acdf2620b..a4fff1f471f29a 100644 --- a/lib/util/result.ts +++ b/lib/util/result.ts @@ -1,5 +1,10 @@ -import type { SafeParseReturnType, ZodType, ZodTypeDef } from 'zod'; -import { ZodError, z } from 'zod'; +import type { + SafeParseReturnType, + input as ZodInput, + output as ZodOutput, + ZodType, +} from 'zod'; +import { NEVER, ZodError, ZodIssueCode } from 'zod'; import { logger } from '../logger'; type Val = NonNullable; @@ -532,30 +537,26 @@ export class Result { * Given a `schema` and `input`, returns a `Result` with `val` being the parsed value. * Additionally, `null` and `undefined` values are converted into Zod error. */ - static parse< - T, - Schema extends ZodType, - Input = unknown, - >( + static parse>( input: unknown, schema: Schema, - ): Result>, ZodError> { + ): Result>, ZodError>> { const parseResult = schema - .transform((result, ctx): NonNullable => { + .transform((result, ctx): NonNullable> => { if (result === undefined) { ctx.addIssue({ - code: z.ZodIssueCode.custom, + code: ZodIssueCode.custom, message: `Result can't accept nullish values, but input was parsed by Zod schema to undefined`, }); - return z.NEVER; + return NEVER; } if (result === null) { ctx.addIssue({ - code: z.ZodIssueCode.custom, + code: ZodIssueCode.custom, message: `Result can't accept nullish values, but input was parsed by Zod schema to null`, }); - return z.NEVER; + return NEVER; } return result; @@ -569,9 +570,9 @@ export class Result { * Given a `schema`, returns a `Result` with `val` being the parsed value. * Additionally, `null` and `undefined` values are converted into Zod error. */ - parse, Input = unknown>( + parse>( schema: Schema, - ): Result>, E | ZodError> { + ): Result>, E | ZodError>> { if (this.res.ok) { return Result.parse(this.res.val, schema); } @@ -862,9 +863,12 @@ export class AsyncResult * Given a `schema`, returns a `Result` with `val` being the parsed value. * Additionally, `null` and `undefined` values are converted into Zod error. */ - parse, Input = unknown>( + parse>( schema: Schema, - ): AsyncResult>, E | ZodError> { + ): AsyncResult< + NonNullable>, + E | ZodError> + > { return new AsyncResult( this.asyncResult .then((oldResult) => oldResult.parse(schema)) diff --git a/lib/util/schema-utils.spec.ts b/lib/util/schema-utils.spec.ts index ccca2f70b9e8e1..7561b17287c81f 100644 --- a/lib/util/schema-utils.spec.ts +++ b/lib/util/schema-utils.spec.ts @@ -1,5 +1,6 @@ import { codeBlock } from 'common-tags'; import { z } from 'zod'; +import { logger } from '../../test/util'; import { Json, Json5, @@ -10,6 +11,9 @@ import { Toml, UtcDate, Yaml, + multidocYaml, + withDebugMessage, + withTraceMessage, } from './schema-utils'; describe('util/schema-utils', () => { @@ -444,6 +448,26 @@ describe('util/schema-utils', () => { }); }); + describe('multidocYaml()', () => { + const Schema = multidocYaml().pipe( + z.array( + z.object({ + foo: z.number(), + }), + ), + ); + + it('parses valid yaml', () => { + expect( + Schema.parse(codeBlock` + foo: 111 + --- + foo: 222 + `), + ).toEqual([{ foo: 111 }, { foo: 222 }]); + }); + }); + describe('Toml', () => { const Schema = Toml.pipe( z.object({ foo: z.object({ bar: z.literal('baz') }) }), @@ -494,4 +518,34 @@ describe('util/schema-utils', () => { }); }); }); + + describe('logging utils', () => { + it('logs debug message and returns fallback value', () => { + const Schema = z + .string() + .catch(withDebugMessage('default string', 'Debug message')); + + const result = Schema.parse(42); + + expect(result).toBe('default string'); + expect(logger.logger.debug).toHaveBeenCalledWith( + { err: expect.any(z.ZodError) }, + 'Debug message', + ); + }); + + it('logs trace message and returns fallback value', () => { + const Schema = z + .string() + .catch(withTraceMessage('default string', 'Trace message')); + + const result = Schema.parse(42); + + expect(result).toBe('default string'); + expect(logger.logger.trace).toHaveBeenCalledWith( + { err: expect.any(z.ZodError) }, + 'Trace message', + ); + }); + }); }); diff --git a/lib/util/schema-utils.ts b/lib/util/schema-utils.ts index de637250c8f9cf..4143400106c921 100644 --- a/lib/util/schema-utils.ts +++ b/lib/util/schema-utils.ts @@ -2,9 +2,17 @@ import JSON5 from 'json5'; import * as JSONC from 'jsonc-parser'; import { DateTime } from 'luxon'; import type { JsonArray, JsonValue } from 'type-fest'; -import { type ZodEffects, type ZodType, type ZodTypeDef, z } from 'zod'; +import { + type ZodEffects, + type ZodString, + type ZodType, + type ZodTypeDef, + z, +} from 'zod'; +import { logger } from '../logger'; import type { PackageDependency } from '../modules/manager/types'; import { parse as parseToml } from './toml'; +import type { YamlOptions } from './yaml'; import { parseSingleYaml, parseYaml } from './yaml'; interface ErrorContext { @@ -219,7 +227,7 @@ 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); + const value = JSONC.parse(str, errors, { allowTrailingComma: true }); if (errors.length === 0) { return value; } @@ -256,6 +264,19 @@ export const MultidocYaml = z.string().transform((str, ctx): JsonArray => { } }); +export function multidocYaml( + opts?: Omit, +): ZodEffects { + return z.string().transform((str, ctx): JsonArray => { + try { + return parseYaml(str, opts) as JsonArray; + } catch { + ctx.addIssue({ code: 'custom', message: 'Invalid YAML' }); + return z.NEVER; + } + }); +} + export const Toml = z.string().transform((str, ctx) => { try { return parseToml(str); @@ -278,3 +299,23 @@ export function withDepType< return deps; }); } + +export function withDebugMessage( + value: Output, + msg: string, +): (ctx: { error: z.ZodError; input: Input }) => Output { + return ({ error: err }) => { + logger.debug({ err }, msg); + return value; + }; +} + +export function withTraceMessage( + value: Output, + msg: string, +): (ctx: { error: z.ZodError; input: Input }) => Output { + return ({ error: err }) => { + logger.trace({ err }, msg); + return value; + }; +} diff --git a/lib/util/timestamp.spec.ts b/lib/util/timestamp.spec.ts new file mode 100644 index 00000000000000..9b7efe3732c2a8 --- /dev/null +++ b/lib/util/timestamp.spec.ts @@ -0,0 +1,39 @@ +import { MaybeTimestamp, asTimestamp } from './timestamp'; + +describe('util/timestamp', () => { + describe('asTimestamp', () => { + test.each` + input | expected + ${new Date('2021-01-01T00:00:00.000Z')} | ${'2021-01-01T00:00:00.000Z'} + ${new Date('2021-01-01T00:00:00.000-03:00')} | ${'2021-01-01T03:00:00.000Z'} + ${new Date('1999-01-01T00:00:00.000Z')} | ${null} + ${1609459200000} | ${'2021-01-01T00:00:00.000Z'} + ${1609459200} | ${'2021-01-01T00:00:00.000Z'} + ${-1} | ${null} + ${0} | ${null} + ${123} | ${null} + ${NaN} | ${null} + ${'2021-01-01T00:00:00.000Z'} | ${'2021-01-01T00:00:00.000Z'} + ${'2021-01-01'} | ${'2021-01-01T00:00:00.000Z'} + ${'20210101000000'} | ${'2021-01-01T00:00:00.000Z'} + ${'20211231235959'} | ${'2021-12-31T23:59:59.000Z'} + ${'20210101000000+0000'} | ${'2021-01-01T00:00:00.000Z'} + ${'20211231235959+0000'} | ${'2021-12-31T23:59:59.000Z'} + ${'Jan 1, 2021'} | ${'2021-01-01T00:00:00.000Z'} + ${'2021/01/01'} | ${'2021-01-01T00:00:00.000Z'} + ${'2021-01-02T00:00:00+05:30'} | ${'2021-01-01T18:30:00.000Z'} + ${'2010-05-20T22:43:19-07:00'} | ${'2010-05-21T05:43:19.000Z'} + ${'2021-10-11 07:47:24 -0700'} | ${'2021-10-11T14:47:24.000Z'} + ${'Wed, 21 Oct 2015 07:28:00 GMT'} | ${'2015-10-21T07:28:00.000Z'} + ${null} | ${null} + ${undefined} | ${null} + ${{}} | ${null} + ${[]} | ${null} + ${'invalid date'} | ${null} + ${'202x0101000000'} | ${null} + `('$input -> $expected', ({ input, expected }) => { + expect(asTimestamp(input)).toBe(expected); + expect(MaybeTimestamp.parse(input)).toBe(expected); + }); + }); +}); diff --git a/lib/util/timestamp.ts b/lib/util/timestamp.ts new file mode 100644 index 00000000000000..adf44b1c346ca2 --- /dev/null +++ b/lib/util/timestamp.ts @@ -0,0 +1,102 @@ +import { DateTime } from 'luxon'; +import { z } from 'zod'; + +export type Timestamp = string & { __timestamp: never }; + +const timezoneOffset = new Date().getTimezoneOffset() * 60000; + +const millenium = 946684800000; // 2000-01-01T00:00:00.000Z +const tomorrowOffset = 86400000; // 24 * 60 * 60 * 1000; + +function isValid(date: DateTime): boolean { + if (!date.isValid) { + return false; + } + const tomorrow = DateTime.now().toMillis() + tomorrowOffset; // 24 * 60 * 60 * 1000; + const ts = date.toMillis(); + return ts > millenium && ts < tomorrow; +} + +export function asTimestamp(input: unknown): Timestamp | null { + if (input instanceof Date) { + const date = DateTime.fromJSDate(input, { zone: 'UTC' }); + if (isValid(date)) { + return date.toISO() as Timestamp; + } + + return null; + } + + if (typeof input === 'number') { + const millisDate = DateTime.fromMillis(input, { zone: 'UTC' }); + if (isValid(millisDate)) { + return millisDate.toISO() as Timestamp; + } + + const secondsDate = DateTime.fromSeconds(input, { zone: 'UTC' }); + if (isValid(secondsDate)) { + return secondsDate.toISO() as Timestamp; + } + + return null; + } + + if (typeof input === 'string') { + const isoDate = DateTime.fromISO(input, { zone: 'UTC' }); + if (isValid(isoDate)) { + return isoDate.toISO() as Timestamp; + } + + const httpDate = DateTime.fromHTTP(input, { zone: 'UTC' }); + if (isValid(httpDate)) { + return httpDate.toISO() as Timestamp; + } + + const sqlDate = DateTime.fromSQL(input, { zone: 'UTC' }); + if (isValid(sqlDate)) { + return sqlDate.toISO() as Timestamp; + } + + const numberLikeDate = DateTime.fromFormat(input, 'yyyyMMddHHmmss', { + zone: 'UTC', + }); + if (isValid(numberLikeDate)) { + return numberLikeDate.toISO() as Timestamp; + } + + const numberLikeOffsetDate = DateTime.fromFormat( + input, + 'yyyyMMddHHmmssZZZ', + { zone: 'UTC' }, + ); + if (isValid(numberLikeOffsetDate)) { + return numberLikeOffsetDate.toISO() as Timestamp; + } + + const fallbackDate = DateTime.fromMillis( + Date.parse(input) - timezoneOffset, + { zone: 'UTC' }, + ); + if (isValid(fallbackDate)) { + return fallbackDate.toISO() as Timestamp; + } + + return null; + } + + return null; +} + +export const Timestamp = z.unknown().transform((input, ctx) => { + const timestamp = asTimestamp(input); + if (!timestamp) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Invalid timestamp', + }); + return z.NEVER; + } + + return timestamp; +}); +export const MaybeTimestamp = Timestamp.nullable().catch(null); diff --git a/lib/util/yaml.ts b/lib/util/yaml.ts index e8d5ef50ae2eae..ff78f31db54246 100644 --- a/lib/util/yaml.ts +++ b/lib/util/yaml.ts @@ -1,5 +1,6 @@ import type { CreateNodeOptions, + Document, DocumentOptions, ParseOptions, SchemaOptions, @@ -10,7 +11,7 @@ import type { ZodType } from 'zod'; import { logger } from '../logger'; import { regEx } from './regex'; -interface YamlOptions< +export interface YamlOptions< ResT = unknown, Schema extends ZodType = ZodType, > extends ParseOptions, @@ -20,7 +21,14 @@ interface YamlOptions< removeTemplates?: boolean; } -interface YamlOptionsMultiple< +interface YamlParseDocumentOptions + extends ParseOptions, + DocumentOptions, + SchemaOptions { + removeTemplates?: boolean; +} + +export interface YamlOptionsMultiple< ResT = unknown, Schema extends ZodType = ZodType, > extends YamlOptions { @@ -117,6 +125,29 @@ export function parseSingleYaml( content: string, options?: YamlOptions, ): ResT { + const rawDocument = parseSingleYamlDocument(content, options); + + const document = rawDocument.toJS({ maxAliasCount: 10000 }); + const schema = options?.customSchema; + if (!schema) { + return document as ResT; + } + + return schema.parse(document); +} + +/** + * Parse a YAML string into a Document representation. + * + * Only a single document is supported. + * + * @param content + * @param options + */ +export function parseSingleYamlDocument( + content: string, + options?: YamlParseDocumentOptions, +): Document { const massagedContent = massageContent(content, options); const rawDocument = parseDocument( massagedContent, @@ -127,13 +158,7 @@ export function parseSingleYaml( throw new AggregateError(rawDocument.errors, 'Failed to parse YAML file'); } - const document = rawDocument.toJS({ maxAliasCount: 10000 }); - const schema = options?.customSchema; - if (!schema) { - return document as ResT; - } - - return schema.parse(document); + return rawDocument; } export function dump(obj: any, opts?: DumpOptions): string { diff --git a/lib/workers/global/config/parse/cli.ts b/lib/workers/global/config/parse/cli.ts index 7830852e74e2aa..33550e7fe84c45 100644 --- a/lib/workers/global/config/parse/cli.ts +++ b/lib/workers/global/config/parse/cli.ts @@ -20,6 +20,11 @@ export function getConfig(input: string[]): AllConfig { const argv = input .map((a) => a + .replace( + '--allow-post-upgrade-command-templating', + '--allow-command-templating', + ) + .replace('--allowed-post-upgrade-commands', '--allowed-commands') .replace('--endpoints=', '--host-rules=') .replace('--expose-env=true', '--trust-level=high') .replace('--expose-env', '--trust-level=high') diff --git a/lib/workers/global/config/parse/env.ts b/lib/workers/global/config/parse/env.ts index 79ed110d6b5be0..493d8969a09b2c 100644 --- a/lib/workers/global/config/parse/env.ts +++ b/lib/workers/global/config/parse/env.ts @@ -42,6 +42,8 @@ const renameKeys = { gitLabAutomerge: 'platformAutomerge', // migrate: gitLabAutomerge mergeConfidenceApiBaseUrl: 'mergeConfidenceEndpoint', mergeConfidenceSupportedDatasources: 'mergeConfidenceDatasources', + allowPostUpgradeCommandTemplating: 'allowCommandTemplating', + allowedPostUpgradeCommands: 'allowedCommands', }; function renameEnvKeys(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv { diff --git a/lib/workers/global/config/parse/file.ts b/lib/workers/global/config/parse/file.ts index cf6ecf20c69898..ec42d187db7c95 100644 --- a/lib/workers/global/config/parse/file.ts +++ b/lib/workers/global/config/parse/file.ts @@ -59,7 +59,7 @@ export async function getConfig(env: NodeJS.ProcessEnv): Promise { config = await getParsedContent(configFile); } catch (err) { if (err instanceof SyntaxError || err instanceof TypeError) { - logger.fatal(`Could not parse config file \n ${err.stack!}`); + logger.fatal({ error: err.stack }, 'Could not parse config file'); process.exit(1); } else if (err instanceof ReferenceError) { logger.fatal( diff --git a/lib/workers/global/config/parse/util.ts b/lib/workers/global/config/parse/util.ts index 9d007c8df4b4d8..2d0720599ea408 100644 --- a/lib/workers/global/config/parse/util.ts +++ b/lib/workers/global/config/parse/util.ts @@ -12,8 +12,8 @@ export async function migrateAndValidateConfig( const { isMigrated, migratedConfig } = migrateConfig(config); if (isMigrated) { logger.warn( - { originalConfig: config, migratedConfig }, - `${configType} needs migrating`, + { configType, originalConfig: config, migratedConfig }, + 'Config needs migrating', ); } const massagedConfig = massageConfig(migratedConfig); @@ -25,13 +25,10 @@ export async function migrateAndValidateConfig( const { warnings, errors } = await validateConfig('global', massagedConfig); if (warnings.length) { - logger.warn( - { warnings }, - `Config validation warnings found in ${configType}`, - ); + logger.warn({ configType, warnings }, 'Config validation warnings found'); } if (errors.length) { - logger.warn({ errors }, `Config validation errors found in ${configType}`); + logger.warn({ configType, errors }, 'Config validation errors found'); } return massagedConfig; diff --git a/lib/workers/global/index.ts b/lib/workers/global/index.ts index 488be90108dfd7..c774fe71610494 100644 --- a/lib/workers/global/index.ts +++ b/lib/workers/global/index.ts @@ -221,9 +221,12 @@ export async function start(): Promise { await exportStats(config); } catch (err) /* istanbul ignore next */ { if (err.message.startsWith('Init: ')) { - logger.fatal(err.message.substring(6)); + logger.fatal( + { errorMessage: err.message.substring(6) }, + 'Initialization error', + ); } else { - logger.fatal({ err }, `Fatal error: ${String(err.message)}`); + logger.fatal({ err }, 'Unknown error'); } if (!config!) { // return early if we can't parse config options diff --git a/lib/workers/global/limits.spec.ts b/lib/workers/global/limits.spec.ts index 84dc44687e5c40..2a3b43997faca0 100644 --- a/lib/workers/global/limits.spec.ts +++ b/lib/workers/global/limits.spec.ts @@ -1,7 +1,13 @@ +import { partial } from '../../../test/util'; +import type { BranchConfig, BranchUpgradeConfig } from '../types'; import { + calcLimit, + hasMultipleLimits, + incCountValue, incLimitedValue, isLimitReached, resetAllLimits, + setCount, setMaxLimit, } from './limits'; @@ -60,4 +66,242 @@ describe('workers/global/limits', () => { setMaxLimit('Commits', -1000); expect(isLimitReached('Commits')).toBeTrue(); }); + + describe('calcLimit', () => { + it('handles single upgrade', () => { + const upgrades = partial([ + { + prHourlyLimit: 10, + branchConcurrentLimit: 11, + prConcurrentLimit: 12, + }, + ]); + + expect(calcLimit(upgrades, 'prHourlyLimit')).toBe(10); + expect(calcLimit(upgrades, 'branchConcurrentLimit')).toBe(11); + expect(calcLimit(upgrades, 'prConcurrentLimit')).toBe(12); + }); + + it('inherits prConcurrentLimit if branchConcurrentLimit is null', () => { + const upgrades = partial([ + { + prHourlyLimit: 10, + branchConcurrentLimit: null, + prConcurrentLimit: 12, + }, + ]); + + expect(calcLimit(upgrades, 'prHourlyLimit')).toBe(10); + expect(calcLimit(upgrades, 'branchConcurrentLimit')).toBe(12); + expect(calcLimit(upgrades, 'prConcurrentLimit')).toBe(12); + }); + + it('returns 0 if atleast one upgrade has no limit in the branch', () => { + const upgrades = partial([ + { + prHourlyLimit: 10, + branchConcurrentLimit: 11, + prConcurrentLimit: 12, + }, + { + prHourlyLimit: 0, + branchConcurrentLimit: 0, + prConcurrentLimit: 0, + }, + { + prHourlyLimit: 1, + branchConcurrentLimit: 1, + prConcurrentLimit: 1, + }, + ]); + + expect(calcLimit(upgrades, 'prHourlyLimit')).toBe(0); + expect(calcLimit(upgrades, 'branchConcurrentLimit')).toBe(0); + expect(calcLimit(upgrades, 'prConcurrentLimit')).toBe(0); + }); + + it('computes the lowest limit if multiple limits are present', () => { + const upgrades = partial([ + { + prHourlyLimit: 10, + branchConcurrentLimit: 11, + prConcurrentLimit: 12, + }, + { + prHourlyLimit: 10, + branchConcurrentLimit: 11, + prConcurrentLimit: 12, + }, + { + prHourlyLimit: 1, + branchConcurrentLimit: 1, + prConcurrentLimit: 1, + }, + { + prHourlyLimit: 5, + branchConcurrentLimit: 6, + prConcurrentLimit: 3, + }, + { + prHourlyLimit: 5, + branchConcurrentLimit: null, + prConcurrentLimit: undefined, + }, + { + prHourlyLimit: 5, + branchConcurrentLimit: 6, + prConcurrentLimit: 2, + }, + ]); + + expect(calcLimit(upgrades, 'prHourlyLimit')).toBe(1); + expect(calcLimit(upgrades, 'branchConcurrentLimit')).toBe(1); + expect(calcLimit(upgrades, 'prConcurrentLimit')).toBe(1); + }); + }); + + describe('hasMultipleLimits', () => { + it('handles single limit', () => { + const upgrades = partial([ + { + prHourlyLimit: 10, + branchConcurrentLimit: 11, + prConcurrentLimit: 12, + }, + ]); + expect(hasMultipleLimits(upgrades, 'prHourlyLimit')).toBe(false); + expect(hasMultipleLimits(upgrades, 'branchConcurrentLimit')).toBe(false); + expect(hasMultipleLimits(upgrades, 'prConcurrentLimit')).toBe(false); + }); + + it('returns false if there are multiple limits with value', () => { + const upgrades = partial([ + { + prHourlyLimit: 10, + branchConcurrentLimit: 11, + prConcurrentLimit: 12, + }, + { + prHourlyLimit: 10, + branchConcurrentLimit: 11, + prConcurrentLimit: 12, + }, + ]); + expect(hasMultipleLimits(upgrades, 'prHourlyLimit')).toBe(false); + expect(hasMultipleLimits(upgrades, 'branchConcurrentLimit')).toBe(false); + expect(hasMultipleLimits(upgrades, 'prConcurrentLimit')).toBe(false); + }); + + it('handles multiple limits', () => { + const upgrades = partial([ + { + prHourlyLimit: 10, + branchConcurrentLimit: 11, + prConcurrentLimit: 12, + }, + { + prHourlyLimit: 11, + branchConcurrentLimit: 12, + prConcurrentLimit: 13, + }, + { + prHourlyLimit: 0, + branchConcurrentLimit: null, + prConcurrentLimit: 3, + }, + ]); + expect(hasMultipleLimits(upgrades, 'prHourlyLimit')).toBe(true); + expect(hasMultipleLimits(upgrades, 'branchConcurrentLimit')).toBe(true); + expect(hasMultipleLimits(upgrades, 'prConcurrentLimit')).toBe(true); + }); + }); + + describe('isLimitReached', () => { + it('returns false based on concurrent limits', () => { + setCount('ConcurrentPRs', 1); + setCount('HourlyPRs', 1); + incCountValue('Branches'); // using incCountValue so it gets test coverage + const upgrades = partial([ + { + prHourlyLimit: 10, + branchConcurrentLimit: 11, + prConcurrentLimit: 12, + }, + { + prHourlyLimit: 11, + branchConcurrentLimit: 12, + prConcurrentLimit: 13, + }, + { + prHourlyLimit: 0, + branchConcurrentLimit: null, + prConcurrentLimit: 3, + }, + ]); + expect( + isLimitReached('Branches', partial({ upgrades })), + ).toBe(false); + expect( + isLimitReached('ConcurrentPRs', partial({ upgrades })), + ).toBe(false); + }); + + it('returns true when hourly limit is reached', () => { + setCount('Branches', 2); + setCount('ConcurrentPRs', 2); + setCount('HourlyPRs', 2); + const upgrades = partial([ + { + prHourlyLimit: 10, + branchConcurrentLimit: 11, + prConcurrentLimit: 12, + }, + { + prHourlyLimit: 11, + branchConcurrentLimit: 12, + prConcurrentLimit: 13, + }, + { + prHourlyLimit: 2, + branchConcurrentLimit: null, + prConcurrentLimit: 3, + }, + ]); + expect( + isLimitReached('Branches', partial({ upgrades })), + ).toBe(true); + expect( + isLimitReached('ConcurrentPRs', partial({ upgrades })), + ).toBe(true); + }); + + it('returns true when concurrent limit is reached', () => { + setCount('Branches', 3); + setCount('ConcurrentPRs', 3); + setCount('HourlyPRs', 4); + const upgrades = partial([ + { + prHourlyLimit: 10, + branchConcurrentLimit: 11, + prConcurrentLimit: 12, + }, + { + prHourlyLimit: 11, + branchConcurrentLimit: 12, + prConcurrentLimit: 13, + }, + { + prHourlyLimit: 5, + branchConcurrentLimit: null, + prConcurrentLimit: 3, + }, + ]); + expect( + isLimitReached('Branches', partial({ upgrades })), + ).toBe(true); + expect( + isLimitReached('ConcurrentPRs', partial({ upgrades })), + ).toBe(true); + }); + }); }); diff --git a/lib/workers/global/limits.ts b/lib/workers/global/limits.ts index aa030b05950647..7a68b3f260559a 100644 --- a/lib/workers/global/limits.ts +++ b/lib/workers/global/limits.ts @@ -1,7 +1,8 @@ +import is from '@sindresorhus/is'; import { logger } from '../../logger'; +import type { BranchConfig, BranchUpgradeConfig } from '../types'; -export type Limit = 'Commits' | 'PullRequests' | 'Branches'; - +export type Limit = 'Commits'; interface LimitValue { max: number | null; current: number; @@ -27,8 +28,8 @@ export function incLimitedValue(key: Limit, incBy = 1): void { }); } -export function isLimitReached(key: Limit): boolean { - const limit = limits.get(key); +function handleCommitsLimit(): boolean { + const limit = limits.get('Commits'); // TODO: fix me? // eslint-disable-next-line @typescript-eslint/prefer-optional-chain if (!limit || limit.max === null) { @@ -37,3 +38,162 @@ export function isLimitReached(key: Limit): boolean { const { max, current } = limit; return max - current <= 0; } + +export type CountName = 'ConcurrentPRs' | 'HourlyPRs' | 'Branches'; + +type BranchLimitName = + | 'branchConcurrentLimit' + | 'prConcurrentLimit' + | 'prHourlyLimit'; + +export const counts = new Map(); + +export function getCount(key: CountName): number { + const count = counts.get(key); + // istanbul ignore if: should not happen + if (!is.integer(count)) { + logger.debug(`Could not compute the count of ${key}, returning zero.`); + return 0; + } + return count; +} + +export function setCount(key: CountName, val: number): void { + counts.set(key, val); + logger.debug(`${key} count = ${val}`); +} + +export function incCountValue(key: CountName, incBy = 1): void { + const count = getCount(key); + counts.set(key, count + incBy); +} + +function handleConcurrentLimits( + key: Exclude, + config: BranchConfig, +): boolean { + const limitKey = + key === 'Branches' ? 'branchConcurrentLimit' : 'prConcurrentLimit'; + + // calculate the limits for this branch + const hourlyLimit = calcLimit(config.upgrades, 'prHourlyLimit'); + const hourlyPrCount = getCount('HourlyPRs'); + + // if a limit is defined ( >0 ) and limit reached return true ie. limit has been reached + if (hourlyLimit && hourlyPrCount >= hourlyLimit) { + return true; + } + + const limitValue = calcLimit(config.upgrades, limitKey); + const currentCount = getCount(key); + + if (limitValue && currentCount >= limitValue) { + return true; + } + + return false; +} + +export function calcLimit( + upgrades: BranchUpgradeConfig[], + limitName: BranchLimitName, +): number { + logger.debug( + { + limits: upgrades.map((upg) => { + return { depName: upg.depName, [limitName]: upg[limitName] }; + }), + }, + `${limitName} of the upgrades present in this branch`, + ); + + if (hasMultipleLimits(upgrades, limitName)) { + logger.once.debug( + `Branch has multiple ${limitName} limits. The lowest among these will be selected.`, + ); + } + + let lowestLimit = Number.MAX_SAFE_INTEGER; + for (const upgrade of upgrades) { + let limit = upgrade[limitName]; + + // inherit prConcurrentLimit value incase branchConcurrentLimit is null + if (!is.number(limit) && limitName === 'branchConcurrentLimit') { + limit = upgrade.prConcurrentLimit; + } + + // istanbul ignore if: should never happen as all limits get a default value + if (is.undefined(limit)) { + limit = Number.MAX_SAFE_INTEGER; + } + + // no limit + if (limit === 0 || limit === null) { + logger.debug( + `${limitName} of this branch is unlimited, because atleast one of the upgrade has it's ${limitName} set to "No limit" ie. 0 or null`, + ); + return 0; + } + + // limit is set + lowestLimit = limit < lowestLimit ? limit : lowestLimit; + } + + logger.debug( + `Calculated lowest ${limitName} among the upgrades present in this branch is ${lowestLimit}.`, + ); + return lowestLimit; +} + +export function hasMultipleLimits( + upgrades: BranchUpgradeConfig[], + limitName: BranchLimitName, +): boolean { + if (upgrades.length === 1) { + return false; + } + + const distinctLimits = new Set(); + for (const upgrade of upgrades) { + let limitValue = upgrade[limitName]; + + // inherit prConcurrentLimit value incase branchConcurrentLimit is null + if (limitName === 'branchConcurrentLimit' && !is.number(limitValue)) { + limitValue = upgrade.prConcurrentLimit; + } + + // istanbul ignore if: should not happen as the limits are of type number + if (limitValue === null) { + limitValue = 0; + } + + if (!is.undefined(limitValue) && !distinctLimits.has(limitValue)) { + distinctLimits.add(limitValue); + } + } + + return distinctLimits.size > 1; +} + +export function isLimitReached(limit: 'Commits'): boolean; +export function isLimitReached( + limit: 'Branches' | 'ConcurrentPRs', + config: BranchConfig, +): boolean; +export function isLimitReached( + limit: 'Commits' | 'Branches' | 'ConcurrentPRs', + config?: BranchConfig, +): boolean { + if (limit === 'Commits') { + return handleCommitsLimit(); + } + + if (config) { + return handleConcurrentLimits(limit, config); + } + + // istanbul ignore next: should not happen + throw new Error( + 'Config is required for computing limits for Branches and PullRequests', + ); +} diff --git a/lib/workers/repository/config-migration/branch/create.spec.ts b/lib/workers/repository/config-migration/branch/create.spec.ts index 45fd3029919ccb..2905fa5582e015 100644 --- a/lib/workers/repository/config-migration/branch/create.spec.ts +++ b/lib/workers/repository/config-migration/branch/create.spec.ts @@ -1,13 +1,14 @@ +import { codeBlock } from 'common-tags'; import type { Indent } from 'detect-indent'; import { Fixtures } from '../../../../../test/fixtures'; import type { RenovateConfig } from '../../../../../test/util'; -import { partial } from '../../../../../test/util'; +import { fs, partial, scm } from '../../../../../test/util'; import { getConfig } from '../../../../config/defaults'; -import { scm } from '../../../../modules/platform/scm'; import { createConfigMigrationBranch } from './create'; import { MigratedDataFactory } from './migrated-data'; import type { MigratedData } from './migrated-data'; +jest.mock('../../../../util/fs'); jest.mock('../../../../util/git'); describe('workers/repository/config-migration/branch/create', () => { @@ -79,6 +80,42 @@ describe('workers/repository/config-migration/branch/create', () => { }); }); + it('migrates renovate config in package.json', async () => { + fs.readLocalFile.mockResolvedValueOnce(codeBlock` + { + "dependencies": { + "xmldoc": "1.0.0" + }, + "renovate": ${renovateConfig} + } + `); + scm.getFileList.mockResolvedValueOnce([]); + await createConfigMigrationBranch(config, { + ...migratedConfigData, + filename: 'package.json', + }); + expect(scm.checkoutBranch).toHaveBeenCalledWith(config.defaultBranch); + expect(scm.commitAndPush).toHaveBeenCalledWith({ + branchName: 'renovate/migrate-config', + baseBranch: 'dev', + files: [ + { + type: 'addition', + path: 'renovate.json', + contents: renovateConfig, + }, + { + type: 'addition', + path: 'package.json', + contents: '{"dependencies":{"xmldoc":"1.0.0"}}', + }, + ], + message: 'Migrate config renovate.json', + platformCommit: 'auto', + force: true, + }); + }); + describe('applies the commitMessagePrefix value', () => { it('to the default commit message', async () => { config.commitMessagePrefix = 'PREFIX:'; diff --git a/lib/workers/repository/config-migration/branch/create.ts b/lib/workers/repository/config-migration/branch/create.ts index 4f920be1493715..73fcc8d6fb5088 100644 --- a/lib/workers/repository/config-migration/branch/create.ts +++ b/lib/workers/repository/config-migration/branch/create.ts @@ -2,9 +2,12 @@ import { GlobalConfig } from '../../../../config/global'; import type { RenovateConfig } from '../../../../config/types'; import { logger } from '../../../../logger'; import { scm } from '../../../../modules/platform/scm'; +import { parseJson } from '../../../../util/common'; +import { readLocalFile } from '../../../../util/fs'; +import type { FileChange } from '../../../../util/git/types'; import { getMigrationBranchName } from '../common'; import { ConfigMigrationCommitMessageFactory } from './commit-message'; -import { MigratedDataFactory } from './migrated-data'; +import { MigratedDataFactory, applyPrettierFormatting } from './migrated-data'; import type { MigratedData } from './migrated-data'; export async function createConfigMigrationBranch( @@ -12,7 +15,10 @@ export async function createConfigMigrationBranch( migratedConfigData: MigratedData, ): Promise { logger.debug('createConfigMigrationBranch()'); - const configFileName = migratedConfigData.filename; + const pJsonMigration = migratedConfigData.filename === 'package.json'; + const configFileName = pJsonMigration + ? 'renovate.json' + : migratedConfigData.filename; logger.debug('Creating config migration branch'); const commitMessageFactory = new ConfigMigrationCommitMessageFactory( @@ -31,16 +37,40 @@ export async function createConfigMigrationBranch( await scm.checkoutBranch(config.defaultBranch!); const contents = await MigratedDataFactory.applyPrettierFormatting(migratedConfigData); + + const files: FileChange[] = [ + { + type: 'addition', + path: configFileName, + contents, + }, + ]; + + if (pJsonMigration) { + const pJson = parseJson( + await readLocalFile('package.json', 'utf8'), + 'package.json', + ) as Record; + if (pJson?.renovate) { + delete pJson.renovate; + } + const pJsonContent = await applyPrettierFormatting( + 'package.json', + JSON.stringify(pJson, undefined, migratedConfigData.indent.indent), + 'json', + migratedConfigData.indent, + ); + files.push({ + type: 'addition', + path: 'package.json', + contents: pJsonContent, + }); + } + return scm.commitAndPush({ baseBranch: config.baseBranch, branchName: getMigrationBranchName(config), - files: [ - { - type: 'addition', - path: configFileName, - contents, - }, - ], + files, message: commitMessage.toString(), platformCommit: config.platformCommit, force: true, diff --git a/lib/workers/repository/config-migration/index.spec.ts b/lib/workers/repository/config-migration/index.spec.ts index 11fa18a755893a..19ae2263eeb0f2 100644 --- a/lib/workers/repository/config-migration/index.spec.ts +++ b/lib/workers/repository/config-migration/index.spec.ts @@ -46,19 +46,6 @@ describe('workers/repository/config-migration/index', () => { expect(ensureConfigMigrationPr).toHaveBeenCalledTimes(0); }); - it('skips pr creation if config found in package.json', async () => { - const branchList: string[] = []; - mockedFunction(MigratedDataFactory.getAsync).mockResolvedValue({ - content, - indent: partial(), - filename: 'package.json', - }); - const res = await configMigration(config, branchList); - expect(res).toMatchObject({ result: 'no-migration' }); - expect(checkConfigMigrationBranch).toHaveBeenCalledTimes(0); - expect(ensureConfigMigrationPr).toHaveBeenCalledTimes(0); - }); - it('creates migration pr if needed', async () => { const branchList: string[] = []; mockedFunction(checkConfigMigrationBranch).mockResolvedValue({ diff --git a/lib/workers/repository/config-migration/index.ts b/lib/workers/repository/config-migration/index.ts index fdd038f3274cb6..2574fdc4eacefb 100644 --- a/lib/workers/repository/config-migration/index.ts +++ b/lib/workers/repository/config-migration/index.ts @@ -27,14 +27,6 @@ export async function configMigration( return { result: 'no-migration' }; } - if (migratedConfigData.filename === 'package.json') { - logger.debug( - ' Using package.json for Renovate config is deprecated - please use a dedicated configuration file instead. Skipping config migration.', - ); - MigratedDataFactory.reset(); - return { result: 'no-migration' }; - } - const res = await checkConfigMigrationBranch(config, migratedConfigData); // migration needed but not demanded by user diff --git a/lib/workers/repository/extract/extract-fingerprint-config.spec.ts b/lib/workers/repository/extract/extract-fingerprint-config.spec.ts index 3f8fe77f905966..eca0d3a980771c 100644 --- a/lib/workers/repository/extract/extract-fingerprint-config.spec.ts +++ b/lib/workers/repository/extract/extract-fingerprint-config.spec.ts @@ -39,7 +39,11 @@ describe('workers/repository/extract/extract-fingerprint-config', () => { ).toEqual({ enabled: true, fileList: [], - fileMatch: ['(^|/)package\\.json$', 'hero.json'], + fileMatch: [ + '(^|/)package\\.json$', + '(^|/)pnpm-workspace\\.yaml$', + 'hero.json', + ], ignorePaths: ['ignore-path-2'], includePaths: ['include-path-2'], manager: 'npm', @@ -85,7 +89,11 @@ describe('workers/repository/extract/extract-fingerprint-config', () => { ).toEqual({ enabled: true, fileList: [], - fileMatch: ['(^|/)package\\.json$', 'hero.json'], + fileMatch: [ + '(^|/)package\\.json$', + '(^|/)pnpm-workspace\\.yaml$', + 'hero.json', + ], ignorePaths: ['**/node_modules/**', '**/bower_components/**'], includePaths: [], manager: 'npm', diff --git a/lib/workers/repository/init/index.spec.ts b/lib/workers/repository/init/index.spec.ts index 54e412fa97d74c..f36df0329d3686 100644 --- a/lib/workers/repository/init/index.spec.ts +++ b/lib/workers/repository/init/index.spec.ts @@ -61,10 +61,12 @@ describe('workers/repository/init/index', () => { ); await initRepo({}); expect(logger.logger.warn).toHaveBeenCalledWith( - "Configuration option 'filterUnavailableUsers' is not supported on the current platform 'undefined'.", + { platform: undefined }, + "Configuration option 'filterUnavailableUsers' is not supported on the current platform.", ); expect(logger.logger.warn).toHaveBeenCalledWith( - "Configuration option 'expandCodeOwnersGroups' is not supported on the current platform 'undefined'.", + { platform: undefined }, + "Configuration option 'expandCodeOwnersGroups' is not supported on the current platform.", ); }); }); diff --git a/lib/workers/repository/init/index.ts b/lib/workers/repository/init/index.ts index e8b36f4433224d..2e016734dfbd9f 100644 --- a/lib/workers/repository/init/index.ts +++ b/lib/workers/repository/init/index.ts @@ -30,7 +30,8 @@ function warnOnUnsupportedOptions(config: RenovateConfig): void { // TODO: types (#22198) const platform = GlobalConfig.get('platform')!; logger.warn( - `Configuration option 'filterUnavailableUsers' is not supported on the current platform '${platform}'.`, + { platform }, + `Configuration option 'filterUnavailableUsers' is not supported on the current platform.`, ); } @@ -38,7 +39,8 @@ function warnOnUnsupportedOptions(config: RenovateConfig): void { // TODO: types (#22198) const platform = GlobalConfig.get('platform')!; logger.warn( - `Configuration option 'expandCodeOwnersGroups' is not supported on the current platform '${platform}'.`, + { platform }, + `Configuration option 'expandCodeOwnersGroups' is not supported on the current platform.`, ); } } diff --git a/lib/workers/repository/init/inherited.spec.ts b/lib/workers/repository/init/inherited.spec.ts index e31f5ba2fc32bf..c375edee9c15ba 100644 --- a/lib/workers/repository/init/inherited.spec.ts +++ b/lib/workers/repository/init/inherited.spec.ts @@ -1,4 +1,4 @@ -import { mocked, platform } from '../../../../test/util'; +import { hostRules, mocked, platform } from '../../../../test/util'; import * as presets_ from '../../../config/presets'; import type { RenovateConfig } from '../../../config/types'; import * as validation from '../../../config/validation'; @@ -25,6 +25,7 @@ describe('workers/repository/init/inherited', () => { inheritConfigFileName: 'config.json', inheritConfigStrict: false, }; + hostRules.clear(); }); it('should return the same config if repository or inheritConfig is not defined', async () => { @@ -91,6 +92,51 @@ describe('workers/repository/init/inherited', () => { expect(logger.warn).not.toHaveBeenCalled(); }); + it('should set hostRules from inherited config', async () => { + platform.getRawFile.mockResolvedValue( + `{ + "hostRules": [ + { + "matchHost": "some-host-url", + "token": "some-token" + } + ] + }`, + ); + const res = await mergeInheritedConfig(config); + expect(hostRules.getAll()).toMatchObject([ + { + matchHost: 'some-host-url', + token: 'some-token', + }, + ]); + expect(res.hostRules).toBeUndefined(); + }); + + it('should apply secrets to inherited config', async () => { + platform.getRawFile.mockResolvedValue( + `{ + "hostRules": [ + { + "matchHost": "some-host-url", + "token": "{{ secrets.SECRET_TOKEN }}" + } + ] + }`, + ); + const res = await mergeInheritedConfig({ + ...config, + secrets: { SECRET_TOKEN: 'some-secret-token' }, + }); + expect(hostRules.getAll()).toMatchObject([ + { + matchHost: 'some-host-url', + token: 'some-secret-token', + }, + ]); + expect(res.hostRules).toBeUndefined(); + }); + it('should resolve presets found in inherited config', async () => { platform.getRawFile.mockResolvedValue( '{"onboarding":false,"labels":["test"],"extends":[":automergeAll"]}', diff --git a/lib/workers/repository/init/inherited.ts b/lib/workers/repository/init/inherited.ts index d924ff825bd37d..aa7266d70897e8 100644 --- a/lib/workers/repository/init/inherited.ts +++ b/lib/workers/repository/init/inherited.ts @@ -3,6 +3,7 @@ import { dequal } from 'dequal'; import { mergeChildConfig, removeGlobalConfig } from '../../../config'; import { parseFileConfig } from '../../../config/parse'; import { resolveConfigPresets } from '../../../config/presets'; +import { applySecretsToConfig } from '../../../config/secrets'; import type { RenovateConfig } from '../../../config/types'; import { validateConfig } from '../../../config/validation'; import { @@ -12,6 +13,9 @@ import { } from '../../../constants/error-messages'; import { logger } from '../../../logger'; import { platform } from '../../../modules/platform'; +import * as hostRules from '../../../util/host-rules'; +import * as queue from '../../../util/http/queue'; +import * as throttle from '../../../util/http/throttle'; import * as template from '../../../util/template'; export async function mergeInheritedConfig( @@ -102,6 +106,8 @@ export async function mergeInheritedConfig( } if (is.nullOrUndefined(filteredConfig.extends)) { + filteredConfig = applySecretsToConfig(filteredConfig, config.secrets ?? {}); + setInheritedHostRules(filteredConfig); return mergeChildConfig(config, filteredConfig); } @@ -137,5 +143,28 @@ export async function mergeInheritedConfig( ); } + filteredConfig = applySecretsToConfig(filteredConfig, config.secrets ?? {}); + setInheritedHostRules(filteredConfig); return mergeChildConfig(config, filteredConfig); } + +function setInheritedHostRules(config: RenovateConfig): void { + if (config.hostRules) { + logger.debug('Setting hostRules from config'); + for (const rule of config.hostRules) { + try { + hostRules.add(rule); + } catch (err) { + // istanbul ignore next + logger.warn( + { err, config: rule }, + 'Error setting hostRule from config', + ); + } + } + // host rules can change concurrency + queue.clear(); + throttle.clear(); + delete config.hostRules; + } +} diff --git a/lib/workers/repository/process/extract-update.ts b/lib/workers/repository/process/extract-update.ts index 8cc0163d84d363..f2a42103ec8c52 100644 --- a/lib/workers/repository/process/extract-update.ts +++ b/lib/workers/repository/process/extract-update.ts @@ -13,6 +13,7 @@ import { extractAllDependencies } from '../extract'; import { generateFingerprintConfig } from '../extract/extract-fingerprint-config'; import { branchifyUpgrades } from '../updates/branchify'; import { fetchUpdates } from './fetch'; +import { calculateLibYears } from './libyear'; import { sortBranches } from './sort'; import { Vulnerabilities } from './vulnerabilities'; import type { WriteUpdateResult } from './write'; @@ -211,6 +212,7 @@ export async function lookup( ): Promise { await fetchVulnerabilities(config, packageFiles); await fetchUpdates(config, packageFiles); + calculateLibYears(packageFiles); const { branches, branchList } = await branchifyUpgrades( config, packageFiles, diff --git a/lib/workers/repository/process/libyear.spec.ts b/lib/workers/repository/process/libyear.spec.ts new file mode 100644 index 00000000000000..bf30b5ab9f4c28 --- /dev/null +++ b/lib/workers/repository/process/libyear.spec.ts @@ -0,0 +1,191 @@ +import { logger } from '../../../../test/util'; +import type { PackageFile } from '../../../modules/manager/types'; +import type { Timestamp } from '../../../util/timestamp'; +import { calculateLibYears } from './libyear'; + +describe('workers/repository/process/libyear', () => { + describe('calculateLibYears', () => { + it('returns early if no packageFiles', () => { + calculateLibYears(undefined); + expect(logger.logger.debug).not.toHaveBeenCalled(); + }); + + it('calculates libYears', () => { + const packageFiles: Record = { + dockerfile: [ + { + packageFile: 'Dockerfile', + deps: [ + { + depName: 'some/image', + datasource: 'docker', + currentVersion: '1.0.0', + updates: [{ newVersion: '2.0.0' }], + }, + ], + }, + ], + npm: [ + { + packageFile: 'package.json', + deps: [ + { + depName: 'dep1', + datasource: 'npm', + currentVersion: '0.1.0', + currentVersionTimestamp: '2019-07-01T00:00:00Z' as Timestamp, + updates: [ + { + newVersion: '1.0.0', + releaseTimestamp: '2020-01-01T00:00:00Z' as Timestamp, + }, + { + newVersion: '2.0.0', + releaseTimestamp: '2020-07-01T00:00:00Z' as Timestamp, + }, + { + newVersion: '3.0.0', + }, + ], + }, + ], + }, + ], + bundler: [ + { + packageFile: 'Gemfile', + deps: [ + { + depName: 'dep2', + currentVersion: '1.0.0', + datasource: 'rubygems', + currentVersionTimestamp: '2019-07-01T00:00:00Z' as Timestamp, + updates: [ + { + newVersion: '2.0.0', + releaseTimestamp: '2020-01-01T00:00:00Z' as Timestamp, + }, + ], + }, + { + depName: 'dep3', + currentVersion: '1.0.0', + datasource: 'rubygems', + updates: [ + { + newVersion: '2.0.0', + releaseTimestamp: '2020-01-01T00:00:00Z' as Timestamp, + }, + ], + }, + { + depName: 'dep4', + datasource: 'rubygems', + currentValue: '1.0.0', // coverage + }, + ], + }, + ], + }; + calculateLibYears(packageFiles); + expect(logger.logger.debug).toHaveBeenCalledWith( + 'No currentVersionTimestamp for some/image', + ); + expect(logger.logger.debug).toHaveBeenCalledWith( + 'No releaseTimestamp for dep1 update to 3.0.0', + ); + expect(logger.logger.debug).toHaveBeenCalledWith( + 'No currentVersionTimestamp for dep3', + ); + expect(logger.logger.debug).toHaveBeenCalledWith( + { + managerLibYears: { + bundler: 0.5027322404371585, + dockerfile: 0, + npm: 1, + }, + // eslint-disable-next-line no-loss-of-precision + totalLibYears: 1.5027322404371585, + totalDepsCount: 5, + outdatedDepsCount: 4, + }, + 'Repository libYears', + ); + }); + + it('de-duplicates if same dep found in different files', () => { + // there are three package files with the same dependency + version but mixed datasources + const packageFiles = { + npm: [ + { + packageFile: 'folder1/package.json', + deps: [ + { + depName: 'dep1', + currentVersion: '0.1.0', + datasource: 'npm', + currentVersionTimestamp: '2019-07-01T00:00:00Z' as Timestamp, + updates: [ + { + newVersion: '1.0.0', + releaseTimestamp: '2020-07-01T00:00:00Z' as Timestamp, + }, + ], + }, + ], + }, + { + packageFile: 'folder2/package.json', + deps: [ + { + depName: 'dep1', + currentVersion: '0.1.0', + datasource: 'npm', + currentVersionTimestamp: '2019-07-01T00:00:00Z' as Timestamp, + updates: [ + { + newVersion: '1.0.0', + releaseTimestamp: '2020-07-01T00:00:00Z' as Timestamp, + }, + ], + }, + ], + }, + ], + regex: [ + { + packageFile: 'folder3/package.json', + deps: [ + { + depName: 'dep1', + currentVersion: '0.1.0', + datsource: 'docker', + currentVersionTimestamp: '2019-07-01T00:00:00Z' as Timestamp, + updates: [ + { + newVersion: '1.0.0', + releaseTimestamp: '2020-07-01T00:00:00Z' as Timestamp, + }, + ], + }, + ], + }, + ], + }; + calculateLibYears(packageFiles); + expect(logger.logger.debug).toHaveBeenCalledWith( + { + managerLibYears: { + npm: 1, + regex: 1, + }, + // eslint-disable-next-line no-loss-of-precision + totalLibYears: 2, + totalDepsCount: 2, + outdatedDepsCount: 2, + }, + 'Repository libYears', + ); + }); + }); +}); diff --git a/lib/workers/repository/process/libyear.ts b/lib/workers/repository/process/libyear.ts new file mode 100644 index 00000000000000..9b2932ccb69ae9 --- /dev/null +++ b/lib/workers/repository/process/libyear.ts @@ -0,0 +1,130 @@ +import { DateTime } from 'luxon'; +import { logger } from '../../../logger'; +import type { PackageFile } from '../../../modules/manager/types'; + +interface DepInfo { + depName: string; + manager: string; + datasource: string; + version: string; + file: string; + outdated?: boolean; + libYear?: number; +} +export function calculateLibYears( + packageFiles?: Record, +): void { + if (!packageFiles) { + return; + } + const allDeps: DepInfo[] = []; + for (const [manager, files] of Object.entries(packageFiles)) { + for (const file of files) { + for (const dep of file.deps) { + const depInfo: DepInfo = { + depName: dep.depName!, + manager, + file: file.packageFile, + datasource: dep.datasource!, + version: (dep.currentVersion ?? dep.currentValue)!, + }; + + if (!dep.updates?.length) { + allDeps.push(depInfo); + continue; + } + + depInfo.outdated = true; + if (!dep.currentVersionTimestamp) { + logger.debug(`No currentVersionTimestamp for ${dep.depName}`); + allDeps.push(depInfo); + continue; + } + // timestamps are in ISO format + const currentVersionDate = DateTime.fromISO( + dep.currentVersionTimestamp, + ); + + for (const update of dep.updates) { + if (!update.releaseTimestamp) { + logger.debug( + `No releaseTimestamp for ${dep.depName} update to ${update.newVersion}`, + ); + continue; + } + const releaseDate = DateTime.fromISO(update.releaseTimestamp); + const libYears = releaseDate.diff(currentVersionDate, 'years').years; + if (libYears >= 0) { + update.libYears = libYears; + } + } + // Set the highest libYears for the dep + const depLibYears = Math.max( + ...dep.updates.map((update) => update.libYears ?? 0), + 0, + ); + depInfo.libYear = depLibYears; + allDeps.push(depInfo); + } + } + } + + const [totalDepsCount, outdatedDepsCount, totalLibYears] = getCounts(allDeps); + logger.debug( + { + managerLibYears: getManagerLibYears(allDeps), + totalLibYears, + totalDepsCount, + outdatedDepsCount, + }, + 'Repository libYears', + ); +} + +function getManagerLibYears(deps: DepInfo[]): Record { + /** {manager : {depKey: libYear }} */ + const managerLibYears: Record> = {}; + for (const dep of deps) { + const depKey = `${dep.depName}@${dep.version}@${dep.datasource}`; + const manager = dep.manager; + managerLibYears[manager] ??= {}; + if (dep.libYear) { + if (!managerLibYears[manager][depKey]) { + managerLibYears[manager][depKey] = dep.libYear; + } + } + } + + const res: Record = {}; + for (const [manager, deps] of Object.entries(managerLibYears)) { + const managerLibYear = Object.values(deps).reduce((sum, curr) => { + return sum + curr; + }, 0); + res[manager] = managerLibYear; + } + + return res; +} + +function getCounts(deps: DepInfo[]): [number, number, number] { + const distinctDeps = new Set(); + let totalDepsCount = 0, + outdatedDepsCount = 0, + totalLibYears = 0; + for (const dep of deps) { + const depKey = `${dep.depName}@${dep.version}@${dep.datasource}`; + if (!distinctDeps.has(depKey)) { + if (dep.outdated) { + outdatedDepsCount++; + } + if (dep.libYear) { + totalLibYears += dep.libYear; + } + + totalDepsCount++; + distinctDeps.add(depKey); + } + } + + return [totalDepsCount, outdatedDepsCount, totalLibYears]; +} diff --git a/lib/workers/repository/process/limits.spec.ts b/lib/workers/repository/process/limits.spec.ts index 93e38106644873..7e9e7030d7abfa 100644 --- a/lib/workers/repository/process/limits.spec.ts +++ b/lib/workers/repository/process/limits.spec.ts @@ -18,8 +18,8 @@ beforeEach(() => { }); describe('workers/repository/process/limits', () => { - describe('getPrHourlyRemaining()', () => { - it('calculates hourly limit remaining', async () => { + describe('getPrHourlyCount()', () => { + it('calculates hourly pr count', async () => { const time = DateTime.local(); const createdAt = time.toISO(); platform.getPrList.mockResolvedValueOnce([ @@ -33,30 +33,19 @@ describe('workers/repository/process/limits', () => { { createdAt, sourceBranch: 'bar/configure' }, { createdAt, sourceBranch: 'baz/test' }, ] as never); - const res = await limits.getPrHourlyRemaining({ - ...config, - prHourlyLimit: 10, - }); - expect(res).toBe(7); + const res = await limits.getPrHourlyCount(config); + expect(res).toBe(3); }); - it('returns prHourlyLimit if errored', async () => { - config.prHourlyLimit = 5; + it('returns zero if errored', async () => { platform.getPrList.mockRejectedValue('Unknown error'); - const res = await limits.getPrHourlyRemaining(config); - expect(res).toBe(5); - }); - - it('returns MAX_SAFE_INTEGER if no hourly limit', async () => { - config.prHourlyLimit = 0; - const res = await limits.getPrHourlyRemaining(config); - expect(res).toBe(Number.MAX_SAFE_INTEGER); + const res = await limits.getPrHourlyCount(config); + expect(res).toBe(0); }); }); - describe('getConcurrentPrsRemaining()', () => { - it('calculates concurrent limit remaining', async () => { - config.prConcurrentLimit = 20; + describe('getConcurrentPrsCount()', () => { + it('calculates concurrent prs present', async () => { platform.getBranchPr.mockImplementation((branchName) => branchName ? Promise.resolve( @@ -71,100 +60,21 @@ describe('workers/repository/process/limits', () => { { branchName: 'test' }, { branchName: null }, ] as never; - const res = await limits.getConcurrentPrsRemaining(config, branches); - expect(res).toBe(19); - }); - - it('returns MAX_SAFE_INTEGER if no concurrent limit', async () => { - config.prConcurrentLimit = 0; - const res = await limits.getConcurrentPrsRemaining(config, []); - expect(res).toBe(Number.MAX_SAFE_INTEGER); - }); - }); - - describe('getPrsRemaining()', () => { - it('returns hourly limit', async () => { - config.prHourlyLimit = 1; - platform.getPrList.mockResolvedValueOnce([]); - const res = await limits.getPrsRemaining(config, []); - expect(res).toBe(1); - }); - - it('returns concurrent limit', async () => { - config.prConcurrentLimit = 1; - const res = await limits.getPrsRemaining(config, []); + const res = await limits.getConcurrentPrsCount(config, branches); expect(res).toBe(1); }); }); - describe('getConcurrentBranchesRemaining()', () => { - it('calculates concurrent limit remaining', async () => { - config.branchConcurrentLimit = 20; - scm.branchExists.mockResolvedValueOnce(true); - const res = await limits.getConcurrentBranchesRemaining(config, [ - { branchName: 'foo' }, - ] as never); - expect(res).toBe(19); - }); - - it('defaults to prConcurrentLimit', async () => { - config.branchConcurrentLimit = null; - config.prConcurrentLimit = 20; - scm.branchExists.mockResolvedValueOnce(true); - const res = await limits.getConcurrentBranchesRemaining(config, [ + describe('getConcurrentBranchesCount()', () => { + it('calculates concurrent branches present', async () => { + scm.branchExists.mockImplementation((branchName) => + branchName ? Promise.resolve(true) : Promise.resolve(false), + ); + const res = await limits.getConcurrentBranchesCount([ { branchName: 'foo' }, + { branchName: null }, ] as never); - expect(res).toBe(19); - }); - - it('does not use prConcurrentLimit for explicit branchConcurrentLimit=0', async () => { - config.branchConcurrentLimit = 0; - config.prConcurrentLimit = 20; - const res = await limits.getConcurrentBranchesRemaining(config, []); - expect(res).toBe(Number.MAX_SAFE_INTEGER); - }); - - it('returns 10 if no limits are set', async () => { - const res = await limits.getConcurrentBranchesRemaining(config, []); - expect(res).toBe(10); - }); - - it('returns prConcurrentLimit if errored', async () => { - config.branchConcurrentLimit = 2; - // TODO: #22198 - const res = await limits.getConcurrentBranchesRemaining( - config, - null as never, - ); - expect(res).toBe(2); - }); - }); - - describe('getBranchesRemaining()', () => { - it('returns minimal of both limits', async () => { - platform.getPrList.mockResolvedValue([]); - - await expect( - limits.getBranchesRemaining( - { - ...config, - prHourlyLimit: 3, - branchConcurrentLimit: 5, - }, - [], - ), - ).resolves.toBe(3); - - await expect( - limits.getBranchesRemaining( - { - ...config, - prHourlyLimit: 11, - branchConcurrentLimit: 7, - }, - [], - ), - ).resolves.toBe(7); + expect(res).toBe(1); }); }); }); diff --git a/lib/workers/repository/process/limits.ts b/lib/workers/repository/process/limits.ts index 55aae025e095c8..fb320f221f7321 100644 --- a/lib/workers/repository/process/limits.ts +++ b/lib/workers/repository/process/limits.ts @@ -1,141 +1,79 @@ import { DateTime } from 'luxon'; import type { RenovateConfig } from '../../../config/types'; import { logger } from '../../../logger'; -import type { Pr } from '../../../modules/platform'; import { platform } from '../../../modules/platform'; import { scm } from '../../../modules/platform/scm'; import { ExternalHostError } from '../../../types/errors/external-host-error'; import type { BranchConfig } from '../../types'; -export async function getPrHourlyRemaining( +export async function getPrHourlyCount( config: RenovateConfig, ): Promise { - if (config.prHourlyLimit) { - try { - logger.debug('Calculating hourly PRs remaining'); - const prList = await platform.getPrList(); - const currentHourStart = DateTime.local().startOf('hour'); - logger.debug(`currentHourStart=${String(currentHourStart)}`); - const soFarThisHour = prList.filter( - (pr) => - pr.sourceBranch !== config.onboardingBranch && - pr.sourceBranch.startsWith(config.branchPrefix!) && - DateTime.fromISO(pr.createdAt!) > currentHourStart, - ); - const prsRemaining = Math.max( - 0, - config.prHourlyLimit - soFarThisHour.length, - ); - logger.debug(`PR hourly limit remaining: ${prsRemaining}`); - return prsRemaining; - } catch (err) { - // istanbul ignore if - if (err instanceof ExternalHostError) { - throw err; - } - logger.error({ err }, 'Error checking PRs created per hour'); - return config.prHourlyLimit; + try { + const prList = await platform.getPrList(); + const currentHourStart = DateTime.local().setZone('utc').startOf('hour'); + logger.debug( + `Calculating PRs created so far in this hour currentHourStart=${String(currentHourStart)}`, + ); + const soFarThisHour = prList.filter( + (pr) => + pr.sourceBranch !== config.onboardingBranch && + pr.sourceBranch.startsWith(config.branchPrefix!) && + DateTime.fromISO(pr.createdAt!) > currentHourStart, + ); + logger.debug( + `${soFarThisHour.length} PRs have been created so far in this hour.`, + ); + return soFarThisHour.length; + } catch (err) { + // istanbul ignore if + if (err instanceof ExternalHostError) { + throw err; } + logger.error({ err }, 'Error checking PRs created per hour'); + return 0; } - return Number.MAX_SAFE_INTEGER; } -export async function getConcurrentPrsRemaining( +export async function getConcurrentPrsCount( config: RenovateConfig, branches: BranchConfig[], ): Promise { - if (config.prConcurrentLimit) { - logger.debug(`Calculating prConcurrentLimit (${config.prConcurrentLimit})`); + let openPrCount = 0; + for (const { branchName } of branches) { try { - const openPrs: Pr[] = []; - for (const { branchName } of branches) { - try { - const pr = await platform.getBranchPr(branchName, config.baseBranch); - if ( - pr && - pr.sourceBranch !== config.onboardingBranch && - pr.state === 'open' - ) { - openPrs.push(pr); - } - } catch (err) { - // istanbul ignore if - if (err instanceof ExternalHostError) { - throw err; - } else { - // no-op - } - } + const pr = await platform.getBranchPr(branchName, config.baseBranch); + if ( + pr && + pr.sourceBranch !== config.onboardingBranch && + pr.state === 'open' + ) { + openPrCount++; + } + } catch (err) { + // istanbul ignore if + if (err instanceof ExternalHostError) { + throw err; + } else { + // no-op } - logger.debug(`${openPrs.length} PRs are currently open`); - const concurrentRemaining = Math.max( - 0, - config.prConcurrentLimit - openPrs.length, - ); - logger.debug(`PR concurrent limit remaining: ${concurrentRemaining}`); - return concurrentRemaining; - } catch (err) /* istanbul ignore next */ { - logger.error({ err }, 'Error checking concurrent PRs'); - return config.prConcurrentLimit; } } - return Number.MAX_SAFE_INTEGER; -} -export async function getPrsRemaining( - config: RenovateConfig, - branches: BranchConfig[], -): Promise { - const hourlyRemaining = await getPrHourlyRemaining(config); - const concurrentRemaining = await getConcurrentPrsRemaining(config, branches); - return Math.min(hourlyRemaining, concurrentRemaining); + logger.debug(`${openPrCount} PRs are currently open`); + return openPrCount; } -export async function getConcurrentBranchesRemaining( - config: RenovateConfig, +export async function getConcurrentBranchesCount( branches: BranchConfig[], ): Promise { - const { branchConcurrentLimit, prConcurrentLimit } = config; - const limit = - typeof branchConcurrentLimit === 'number' - ? branchConcurrentLimit - : prConcurrentLimit; - if (typeof limit === 'number' && limit) { - logger.debug(`Calculating branchConcurrentLimit (${limit})`); - try { - const existingBranches: string[] = []; - for (const branch of branches) { - if (await scm.branchExists(branch.branchName)) { - existingBranches.push(branch.branchName); - } - } - - const existingCount = existingBranches.length; - logger.debug( - `${existingCount} already existing branches found: ${existingBranches.join()}`, - ); - - const concurrentRemaining = Math.max(0, limit - existingCount); - logger.debug(`Branch concurrent limit remaining: ${concurrentRemaining}`); - - return concurrentRemaining; - } catch (err) { - // TODO: #22198 should never throw - logger.error({ err }, 'Error checking concurrent branches'); - return limit; + let existingBranchCount = 0; + for (const branch of branches) { + if (await scm.branchExists(branch.branchName)) { + existingBranchCount++; } } - return Number.MAX_SAFE_INTEGER; -} -export async function getBranchesRemaining( - config: RenovateConfig, - branches: BranchConfig[], -): Promise { - const hourlyRemaining = await getPrHourlyRemaining(config); - const concurrentRemaining = await getConcurrentBranchesRemaining( - config, - branches, - ); - return Math.min(hourlyRemaining, concurrentRemaining); + logger.debug(`${existingBranchCount} already existing branches found.`); + return existingBranchCount; } diff --git a/lib/workers/repository/process/lookup/filter-checks.spec.ts b/lib/workers/repository/process/lookup/filter-checks.spec.ts index aa1f3d63fed6b6..37c0a56d664650 100644 --- a/lib/workers/repository/process/lookup/filter-checks.spec.ts +++ b/lib/workers/repository/process/lookup/filter-checks.spec.ts @@ -13,6 +13,7 @@ import { clone } from '../../../../util/clone'; import * as _dateUtil from '../../../../util/date'; import * as _mergeConfidence from '../../../../util/merge-confidence'; import { toMs } from '../../../../util/pretty-time'; +import type { Timestamp } from '../../../../util/timestamp'; import { filterInternalChecks } from './filter-checks'; import type { LookupUpdateConfig, UpdateResult } from './types'; @@ -42,19 +43,19 @@ const versioning = allVersioning.get('semver'); const releases: Release[] = [ { version: '1.0.1', - releaseTimestamp: '2021-01-01T00:00:01.000Z', + releaseTimestamp: '2021-01-01T00:00:01.000Z' as Timestamp, }, { version: '1.0.2', - releaseTimestamp: '2021-01-03T00:00:00.000Z', + releaseTimestamp: '2021-01-03T00:00:00.000Z' as Timestamp, }, { version: '1.0.3', - releaseTimestamp: '2021-01-05T00:00:00.000Z', + releaseTimestamp: '2021-01-05T00:00:00.000Z' as Timestamp, }, { version: '1.0.4', - releaseTimestamp: '2021-01-07T00:00:00.000Z', + releaseTimestamp: '2021-01-07T00:00:00.000Z' as Timestamp, }, ]; diff --git a/lib/workers/repository/process/lookup/filter.spec.ts b/lib/workers/repository/process/lookup/filter.spec.ts index e445a9b0fdffdb..5cdf6f78b895e9 100644 --- a/lib/workers/repository/process/lookup/filter.spec.ts +++ b/lib/workers/repository/process/lookup/filter.spec.ts @@ -1,6 +1,7 @@ import { partial } from '../../../../../test/util'; import type { Release } from '../../../../modules/datasource/types'; import * as allVersioning from '../../../../modules/versioning'; +import type { Timestamp } from '../../../../util/timestamp'; import { filterVersions } from './filter'; import type { FilterConfig } from './types'; @@ -12,24 +13,24 @@ describe('workers/repository/process/lookup/filter', () => { const releases = [ { version: '1.0.1', - releaseTimestamp: '2021-01-01T00:00:01.000Z', + releaseTimestamp: '2021-01-01T00:00:01.000Z' as Timestamp, }, { version: '1.2.0', - releaseTimestamp: '2021-01-03T00:00:00.000Z', + releaseTimestamp: '2021-01-03T00:00:00.000Z' as Timestamp, }, { version: '2.0.0', - releaseTimestamp: '2021-01-05T00:00:00.000Z', + releaseTimestamp: '2021-01-05T00:00:00.000Z' as Timestamp, }, { version: '2.1.0', - releaseTimestamp: '2021-01-07T00:00:00.000Z', + releaseTimestamp: '2021-01-07T00:00:00.000Z' as Timestamp, }, // for coverage { version: 'invalid.version', - releaseTimestamp: '2021-01-07T00:00:00.000Z', + releaseTimestamp: '2021-01-07T00:00:00.000Z' as Timestamp, }, ] satisfies Release[]; diff --git a/lib/workers/repository/process/lookup/index.spec.ts b/lib/workers/repository/process/lookup/index.spec.ts index 986d8bc395d6f4..31fe79439242b7 100644 --- a/lib/workers/repository/process/lookup/index.spec.ts +++ b/lib/workers/repository/process/lookup/index.spec.ts @@ -31,6 +31,7 @@ import * as memCache from '../../../../util/cache/memory'; import { initConfig, resetConfig } from '../../../../util/merge-confidence'; import * as McApi from '../../../../util/merge-confidence'; import { Result } from '../../../../util/result'; +import type { Timestamp } from '../../../../util/timestamp'; import type { LookupUpdateConfig } from './types'; import * as lookup from '.'; @@ -162,7 +163,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '1.4.1', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'major', }, ]); @@ -219,7 +220,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '^0.9.0', newVersion: '0.9.7', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2013-09-04T17:07:22.948Z', + releaseTimestamp: '2013-09-04T17:07:22.948Z' as Timestamp, updateType: 'minor', }, { @@ -231,7 +232,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '^1.0.0', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'major', }, ]); @@ -261,7 +262,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '^0.4.0', newVersion: '0.4.4', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2011-06-10T17:20:04.719Z', + releaseTimestamp: '2011-06-10T17:20:04.719Z' as Timestamp, updateType: 'patch', }, { @@ -273,7 +274,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '^0.9.0', newVersion: '0.9.7', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2013-09-04T17:07:22.948Z', + releaseTimestamp: '2013-09-04T17:07:22.948Z' as Timestamp, updateType: 'minor', }, { @@ -285,7 +286,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '^1.0.0', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'major', }, ]); @@ -436,7 +437,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '^0.9.0', newVersion: '0.9.7', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2013-09-04T17:07:22.948Z', + releaseTimestamp: '2013-09-04T17:07:22.948Z' as Timestamp, updateType: 'minor', }, { @@ -448,7 +449,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '^1.0.0', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'major', }, ]); @@ -500,7 +501,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '0.9.7', newVersion: '0.9.7', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2013-09-04T17:07:22.948Z', + releaseTimestamp: '2013-09-04T17:07:22.948Z' as Timestamp, updateType: 'minor', }, ]); @@ -526,7 +527,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '0.9.7', newVersion: '0.9.7', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2013-09-04T17:07:22.948Z', + releaseTimestamp: '2013-09-04T17:07:22.948Z' as Timestamp, updateType: 'minor', }, ]); @@ -553,7 +554,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '0.9.7', newVersion: '0.9.7', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2013-09-04T17:07:22.948Z', + releaseTimestamp: '2013-09-04T17:07:22.948Z' as Timestamp, updateType: 'minor', }, ]); @@ -580,7 +581,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '0.9.4', newVersion: '0.9.4', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2013-05-22T20:26:50.888Z', + releaseTimestamp: '2013-05-22T20:26:50.888Z' as Timestamp, updateType: 'minor', }, ]); @@ -699,7 +700,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '0.9.7', newVersion: '0.9.7', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2013-09-04T17:07:22.948Z', + releaseTimestamp: '2013-09-04T17:07:22.948Z' as Timestamp, updateType: 'patch', }, { @@ -710,7 +711,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '1.4.1', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'major', }, ]); @@ -794,7 +795,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '^1.0.0', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'major', }, ]); @@ -821,7 +822,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '1.4.1', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'minor', }, ]); @@ -1059,7 +1060,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '~0.9.0', newVersion: '0.9.7', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2013-09-04T17:07:22.948Z', + releaseTimestamp: '2013-09-04T17:07:22.948Z' as Timestamp, updateType: 'minor', }, { @@ -1071,7 +1072,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '~1.4.0', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'major', }, ]); @@ -1216,7 +1217,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '~1.4.0', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'major', }, ]); @@ -1250,7 +1251,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '~1.4.0', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'minor', }, ]); @@ -1391,7 +1392,7 @@ describe('workers/repository/process/lookup/index', () => { newPatch: 0, newValue: undefined, newVersion: '1.3.0', - releaseTimestamp: '2015-04-26T16:42:11.311Z', + releaseTimestamp: '2015-04-26T16:42:11.311Z' as Timestamp, updateType: 'minor', }, ]); @@ -1420,7 +1421,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '~1.3.0 || ~1.4.0', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'minor', }, ]); @@ -1447,7 +1448,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '~1.4.0', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'minor', }, ]); @@ -1477,7 +1478,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '^2.0.0 || ^3.0.0', newVersion: '3.8.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2017-10-17T15:22:36.646Z', + releaseTimestamp: '2017-10-17T15:22:36.646Z' as Timestamp, updateType: 'major', }, ]); @@ -1507,7 +1508,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '^3.0.0', newVersion: '3.8.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2017-10-17T15:22:36.646Z', + releaseTimestamp: '2017-10-17T15:22:36.646Z' as Timestamp, updateType: 'major', }, ]); @@ -1615,7 +1616,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '~1.4.0', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'minor', }, ]); @@ -1649,7 +1650,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '1.4.x', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'minor', }, ]); @@ -1676,7 +1677,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '~1.4.0', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'minor', }, ]); @@ -1703,7 +1704,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '1.x', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'major', }, ]); @@ -1730,7 +1731,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '1.4.x', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'minor', }, ]); @@ -1757,7 +1758,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '1.2.x - 1.4.x', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'minor', }, ]); @@ -1784,7 +1785,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '1', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'major', }, ]); @@ -1811,7 +1812,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '1.4', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'minor', }, ]); @@ -1838,7 +1839,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '~0.9.0', newVersion: '0.9.7', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2013-09-04T17:07:22.948Z', + releaseTimestamp: '2013-09-04T17:07:22.948Z' as Timestamp, updateType: 'minor', }, { @@ -1850,7 +1851,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '~1.4.0', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'major', }, ]); @@ -1877,7 +1878,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '^0.9.0', newVersion: '0.9.7', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2013-09-04T17:07:22.948Z', + releaseTimestamp: '2013-09-04T17:07:22.948Z' as Timestamp, updateType: 'minor', }, { @@ -1889,7 +1890,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '^1.0.0', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'major', }, ]); @@ -1958,7 +1959,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '^1.0.0 || ^2.0.0 || ^3.0.0', newVersion: '3.8.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2017-10-17T15:22:36.646Z', + releaseTimestamp: '2017-10-17T15:22:36.646Z' as Timestamp, updateType: 'major', }, ]); @@ -1988,7 +1989,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '1.x - 3.x', newVersion: '3.8.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2017-10-17T15:22:36.646Z', + releaseTimestamp: '2017-10-17T15:22:36.646Z' as Timestamp, updateType: 'major', }, ]); @@ -2018,7 +2019,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '1.x || 2.x || 3.x', newVersion: '3.8.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2017-10-17T15:22:36.646Z', + releaseTimestamp: '2017-10-17T15:22:36.646Z' as Timestamp, updateType: 'major', }, ]); @@ -2048,7 +2049,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '1 || 2 || 3', newVersion: '3.8.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2017-10-17T15:22:36.646Z', + releaseTimestamp: '2017-10-17T15:22:36.646Z' as Timestamp, updateType: 'major', }, ]); @@ -2075,7 +2076,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '~1.2.0 || ~1.3.0 || ~1.4.0', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'minor', }, ]); @@ -2116,7 +2117,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '<= 0.9.7', newVersion: '0.9.7', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2013-09-04T17:07:22.948Z', + releaseTimestamp: '2013-09-04T17:07:22.948Z' as Timestamp, updateType: 'minor', }, { @@ -2128,7 +2129,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '<= 1.4.1', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'major', }, ]); @@ -2155,7 +2156,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '< 0.9.8', newVersion: '0.9.7', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2013-09-04T17:07:22.948Z', + releaseTimestamp: '2013-09-04T17:07:22.948Z' as Timestamp, updateType: 'minor', }, { @@ -2167,7 +2168,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '< 1.4.2', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'major', }, ]); @@ -2194,7 +2195,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '< 2', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'major', }, ]); @@ -2221,7 +2222,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '<= 1.4', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'minor', }, ]); @@ -2248,7 +2249,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '=1.4.1', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'minor', }, ]); @@ -2276,7 +2277,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '<= 2', newVersion: '2.0.3', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-01-31T08:11:47.852Z', + releaseTimestamp: '2015-01-31T08:11:47.852Z' as Timestamp, updateType: 'major', }, ]); @@ -2475,7 +2476,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '2.0.3', newVersion: '2.0.3', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-01-31T08:11:47.852Z', + releaseTimestamp: '2015-01-31T08:11:47.852Z' as Timestamp, updateType: 'major', }, ]); @@ -2571,11 +2572,11 @@ describe('workers/repository/process/lookup/index', () => { { version: '1.4.4' }, { version: '1.4.5', - releaseTimestamp: lastWeek.toISOString(), + releaseTimestamp: lastWeek.toISOString() as Timestamp, }, { version: '1.4.6', - releaseTimestamp: yesterday.toISOString(), + releaseTimestamp: yesterday.toISOString() as Timestamp, }, ], }); @@ -2615,11 +2616,11 @@ describe('workers/repository/process/lookup/index', () => { { version: '1.4.4' }, { version: '1.4.5', - releaseTimestamp: lastWeek.toISOString(), + releaseTimestamp: lastWeek.toISOString() as Timestamp, }, { version: '1.4.6', - releaseTimestamp: yesterday.toISOString(), + releaseTimestamp: yesterday.toISOString() as Timestamp, }, ], }); @@ -2916,7 +2917,7 @@ describe('workers/repository/process/lookup/index', () => { { version: 'v2.0.0', gitRef: 'v2.0.0', - releaseTimestamp: '2022-01-01', + releaseTimestamp: '2022-01-01' as Timestamp, }, ], }); @@ -2951,7 +2952,7 @@ describe('workers/repository/process/lookup/index', () => { { version: 'v2.0.0', gitRef: 'v2.0.0', - releaseTimestamp: '2022-01-01', + releaseTimestamp: '2022-01-01' as Timestamp, }, ], }); @@ -2984,7 +2985,7 @@ describe('workers/repository/process/lookup/index', () => { { version: 'v1.0.0', gitRef: 'v1.0.0', - releaseTimestamp: '2022-01-01', + releaseTimestamp: '2022-01-01' as Timestamp, }, ], }); @@ -3051,7 +3052,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '^0.0.35', newVersion: '0.0.35', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2017-04-27T16:59:06.479Z', + releaseTimestamp: '2017-04-27T16:59:06.479Z' as Timestamp, updateType: 'patch', }, ]); @@ -3227,7 +3228,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '^1.4.1', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'minor', }, ]); @@ -3256,7 +3257,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '~1.0.1', newVersion: '1.0.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2014-03-11T18:47:17.560Z', + releaseTimestamp: '2014-03-11T18:47:17.560Z' as Timestamp, updateType: 'patch', }, { @@ -3268,7 +3269,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '~1.4.1', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'minor', }, ]); @@ -3297,7 +3298,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '~1.0.1', newVersion: '1.0.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2014-03-11T18:47:17.560Z', + releaseTimestamp: '2014-03-11T18:47:17.560Z' as Timestamp, updateType: 'patch', }, { @@ -3309,7 +3310,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '~1.4.1', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'minor', }, ]); @@ -3337,7 +3338,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '>=1.4.1', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'minor', }, ]); @@ -3366,7 +3367,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '>=1.4.1', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'major', }, ]); @@ -3435,7 +3436,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '1.4.1', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'minor', }, ]); @@ -3537,7 +3538,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '~=1.4', newVersion: '1.4.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2015-05-17T04:25:07.299Z', + releaseTimestamp: '2015-05-17T04:25:07.299Z' as Timestamp, updateType: 'major', }, ]); @@ -3555,7 +3556,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res).toMatchObject({ currentVersion: '1.3.0', - currentVersionTimestamp: '2015-04-26T16:42:11.311Z', + currentVersionTimestamp: '2015-04-26T16:42:11.311Z' as Timestamp, fixedVersion: '1.3.0', isSingleVersion: true, registryUrl: 'https://registry.npmjs.org', @@ -3598,7 +3599,7 @@ describe('workers/repository/process/lookup/index', () => { ).unwrapOrThrow(); expect(res).toMatchObject({ currentVersion: '1.3.0', - currentVersionTimestamp: '2015-04-26T16:42:11.311Z', + currentVersionTimestamp: '2015-04-26T16:42:11.311Z' as Timestamp, fixedVersion: '1.3.0', isSingleVersion: true, registryUrl: 'https://registry.npmjs.org', @@ -3655,7 +3656,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res).toMatchObject({ currentVersion: '1.3.0', - currentVersionTimestamp: '2015-04-26T16:42:11.311Z', + currentVersionTimestamp: '2015-04-26T16:42:11.311Z' as Timestamp, deprecationMessage: codeBlock` On registry \`https://registry.npmjs.org\`, the "latest" version of dependency \`q3\` has the following deprecation notice: @@ -4424,7 +4425,7 @@ describe('workers/repository/process/lookup/index', () => { // a day old release releaseTimestamp: new Date( Date.now() - 25 * 60 * 60 * 1000, - ).toISOString(), + ).toISOString() as Timestamp, }, { version: '18.0.0', @@ -4466,7 +4467,7 @@ describe('workers/repository/process/lookup/index', () => { ]; const releaseTimestamp = new Date( Date.now() - 25 * 60 * 60 * 1000, - ).toISOString(); + ).toISOString() as Timestamp; getDockerReleases.mockResolvedValueOnce({ releases: [ { @@ -4683,10 +4684,8 @@ describe('workers/repository/process/lookup/index', () => { 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({ @@ -4785,7 +4784,6 @@ describe('workers/repository/process/lookup/index', () => { it('handles replacements - name and version', async () => { config.currentValue = '1.4.1'; config.packageName = 'q'; - // This config is normally set when packageRules are applied config.replacementName = 'r'; config.replacementVersion = '2.0.0'; config.datasource = NpmDatasource.id; @@ -4947,6 +4945,29 @@ describe('workers/repository/process/lookup/index', () => { ]); }); + it('handles replacements - from datasource', async () => { + config.currentValue = '2.0.0'; + config.packageName = 'org.example:foo'; + config.datasource = MavenDatasource.id; + getMavenReleases.mockResolvedValueOnce({ + releases: [{ version: '2.0.0' }], + replacementName: 'foo:bar', + replacementVersion: '2.0.0', + }); + + const { updates } = await Result.wrap( + lookup.lookupUpdates(config), + ).unwrapOrThrow(); + + expect(updates).toEqual([ + { + updateType: 'replacement', + newName: 'foo:bar', + newValue: '2.0.0', + }, + ]); + }); + it('rollback for invalid version to last stable version', async () => { config.currentValue = '2.5.17'; config.packageName = 'vue'; @@ -5033,7 +5054,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '3.8.1', newVersion: '3.8.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2017-10-17T15:22:36.646Z', + releaseTimestamp: '2017-10-17T15:22:36.646Z' as Timestamp, updateType: 'minor', }, ]); @@ -5062,7 +5083,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: '3.8.1', newVersion: '3.8.1', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2017-10-17T15:22:36.646Z', + releaseTimestamp: '2017-10-17T15:22:36.646Z' as Timestamp, updateType: 'minor', }, ]); @@ -5122,7 +5143,7 @@ describe('workers/repository/process/lookup/index', () => { newValue: 'v0.0.0-20240509183442-62759503f434', newVersion: 'v0.0.0-20240509183442-62759503f434', newVersionAgeInDays: expect.any(Number), - releaseTimestamp: '2024-05-09T18:34:42.000Z', + releaseTimestamp: '2024-05-09T18:34:42.000Z' as Timestamp, updateType: 'digest', }, ]); diff --git a/lib/workers/repository/process/lookup/index.ts b/lib/workers/repository/process/lookup/index.ts index e481f6b3acbc0d..7c674c80080dd1 100644 --- a/lib/workers/repository/process/lookup/index.ts +++ b/lib/workers/repository/process/lookup/index.ts @@ -571,6 +571,12 @@ export async function lookupUpdates( if (isReplacementRulesConfigured(config)) { addReplacementUpdateIfValid(res.updates, config); + } else if (dependency?.replacementName && dependency.replacementVersion) { + res.updates.push({ + updateType: 'replacement', + newName: dependency.replacementName, + newValue: dependency.replacementVersion, + }); } // Record if the dep is fixed to a version diff --git a/lib/workers/repository/process/vulnerabilities.spec.ts b/lib/workers/repository/process/vulnerabilities.spec.ts index db30414b2edfa9..8922742ae9def5 100644 --- a/lib/workers/repository/process/vulnerabilities.spec.ts +++ b/lib/workers/repository/process/vulnerabilities.spec.ts @@ -296,8 +296,8 @@ describe('workers/repository/process/vulnerabilities', () => { packageFiles, ); expect(logger.logger.warn).toHaveBeenCalledWith( - { err }, - 'Error fetching vulnerability information for lodash', + { err, packageName: 'lodash' }, + 'Error fetching vulnerability information for package', ); }); @@ -407,7 +407,7 @@ describe('workers/repository/process/vulnerabilities', () => { config, packageFiles, ); - expect(logger.logger.info).toHaveBeenCalledWith( + expect(logger.logger.debug).toHaveBeenCalledWith( 'No fixed version available for vulnerability GHSA-xxxx-yyyy-zzzz in fake 4.17.11', ); }); @@ -449,7 +449,7 @@ describe('workers/repository/process/vulnerabilities', () => { config, packageFiles, ); - expect(logger.logger.info).toHaveBeenCalledWith( + expect(logger.logger.debug).toHaveBeenCalledWith( 'No fixed version available for vulnerability GHSA-xxxx-yyyy-zzzz in fake 1.5.1', ); }); diff --git a/lib/workers/repository/process/vulnerabilities.ts b/lib/workers/repository/process/vulnerabilities.ts index 48991ea669bbcf..96e7af3a3fe6ea 100644 --- a/lib/workers/repository/process/vulnerabilities.ts +++ b/lib/workers/repository/process/vulnerabilities.ts @@ -249,8 +249,8 @@ export class Vulnerabilities { return { vulnerabilities, versioningApi }; } catch (err) { logger.warn( - { err }, - `Error fetching vulnerability information for ${packageName}`, + { err, packageName }, + 'Error fetching vulnerability information for package', ); return null; } @@ -483,7 +483,7 @@ export class Vulnerabilities { packageFileConfig, } = vul; if (is.nullOrUndefined(fixedVersion)) { - logger.info( + logger.debug( `No fixed version available for vulnerability ${vulnerability.id} in ${packageName} ${depVersion}`, ); return null; diff --git a/lib/workers/repository/process/write.spec.ts b/lib/workers/repository/process/write.spec.ts index f3a7405fabba46..70a8165ffc5ff9 100644 --- a/lib/workers/repository/process/write.spec.ts +++ b/lib/workers/repository/process/write.spec.ts @@ -12,7 +12,7 @@ import type { } from '../../../util/cache/repository/types'; import { fingerprint } from '../../../util/fingerprint'; import type { LongCommitSha } from '../../../util/git/types'; -import { isLimitReached } from '../../global/limits'; +import { counts } from '../../global/limits'; import type { BranchConfig, BranchUpgradeConfig } from '../../types'; import * as _branchWorker from '../update/branch'; import * as _limits from './limits'; @@ -32,8 +32,9 @@ const repoCache = mocked(_repoCache); branchWorker.processBranch = jest.fn(); -limits.getPrsRemaining = jest.fn().mockResolvedValue(99); -limits.getBranchesRemaining = jest.fn().mockResolvedValue(99); +limits.getConcurrentPrsCount = jest.fn().mockResolvedValue(0); +limits.getConcurrentBranchesCount = jest.fn().mockResolvedValue(0); +limits.getPrHourlyCount = jest.fn().mockResolvedValue(0); let config: RenovateConfig; @@ -104,22 +105,35 @@ describe('workers/repository/process/write', () => { it('increments branch counter', async () => { const branchName = 'branchName'; - const branches: BranchConfig[] = [ - { baseBranch: 'main', branchName, upgrades: [], manager: 'npm' }, - { baseBranch: 'dev', branchName, upgrades: [], manager: 'npm' }, - ]; + const branches = partial([ + { + baseBranch: 'main', + branchName, + upgrades: partial([{ prConcurrentLimit: 10 }]), + manager: 'npm', + }, + { + baseBranch: 'dev', + branchName, + upgrades: partial([{ prConcurrentLimit: 10 }]), + manager: 'npm', + }, + ]); repoCache.getCache.mockReturnValueOnce({}); branchWorker.processBranch.mockResolvedValueOnce({ branchExists: true, result: 'pr-created', }); - scm.branchExists.mockResolvedValueOnce(false).mockResolvedValueOnce(true); - limits.getBranchesRemaining.mockResolvedValueOnce(1); - expect(isLimitReached('Branches')).toBeFalse(); + + limits.getConcurrentPrsCount.mockResolvedValue(0); + limits.getConcurrentBranchesCount.mockResolvedValue(0); + limits.getPrHourlyCount.mockResolvedValue(0); + + scm.branchExists.mockResolvedValueOnce(false).mockResolvedValue(true); GlobalConfig.set({ dryRun: 'full' }); config.baseBranches = ['main', 'dev']; await writeUpdates(config, branches); - expect(isLimitReached('Branches')).toBeTrue(); + expect(counts.get('Branches')).toBe(1); expect(addMeta).toHaveBeenCalledWith({ baseBranch: 'main', branch: branchName, diff --git a/lib/workers/repository/process/write.ts b/lib/workers/repository/process/write.ts index 4cc9f74c5d3adb..6f7309b86b2f66 100644 --- a/lib/workers/repository/process/write.ts +++ b/lib/workers/repository/process/write.ts @@ -7,11 +7,15 @@ import { getCache } from '../../../util/cache/repository'; import type { BranchCache } from '../../../util/cache/repository/types'; import { fingerprint } from '../../../util/fingerprint'; import { setBranchNewCommit } from '../../../util/git/set-branch-commit'; -import { incLimitedValue, setMaxLimit } from '../../global/limits'; +import { incCountValue, setCount } from '../../global/limits'; import type { BranchConfig, UpgradeFingerprintConfig } from '../../types'; import { processBranch } from '../update/branch'; import { upgradeFingerprintFields } from './fingerprint-fields'; -import { getBranchesRemaining, getPrsRemaining } from './limits'; +import { + getConcurrentBranchesCount, + getConcurrentPrsCount, + getPrHourlyCount, +} from './limits'; export type WriteUpdateResult = 'done' | 'automerged'; @@ -127,15 +131,15 @@ export async function writeUpdates( .sort() .join(', ')}`, ); - const prsRemaining = await getPrsRemaining(config, branches); - logger.debug(`Calculated maximum PRs remaining this run: ${prsRemaining}`); - setMaxLimit('PullRequests', prsRemaining); - const branchesRemaining = await getBranchesRemaining(config, branches); - logger.debug( - `Calculated maximum branches remaining this run: ${branchesRemaining}`, - ); - setMaxLimit('Branches', branchesRemaining); + const concurrentPrsCount = await getConcurrentPrsCount(config, branches); + setCount('ConcurrentPRs', concurrentPrsCount); + + const concurrentBranchesCount = await getConcurrentBranchesCount(branches); + setCount('Branches', concurrentBranchesCount); + + const prsThisHourCount = await getPrHourlyCount(config); + setCount('HourlyPRs', prsThisHourCount); for (const branch of branches) { const { baseBranch, branchName } = branch; @@ -182,7 +186,7 @@ export async function writeUpdates( return 'automerged'; } if (!branchExisted && (await scm.branchExists(branch.branchName))) { - incLimitedValue('Branches'); + incCountValue('Branches'); } } removeMeta(['branch', 'baseBranch']); diff --git a/lib/workers/repository/update/branch/auto-replace.spec.ts b/lib/workers/repository/update/branch/auto-replace.spec.ts index fb06f7bc23bc89..03ada2c1e2423a 100644 --- a/lib/workers/repository/update/branch/auto-replace.spec.ts +++ b/lib/workers/repository/update/branch/auto-replace.spec.ts @@ -1374,5 +1374,43 @@ describe('workers/repository/update/branch/auto-replace', () => { `, ); }); + + it('github-actions: failes to update currentDigestShort', async () => { + const githubAction = codeBlock` + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@2485f4 # tag=v1.0.0 + `; + upgrade.manager = 'github-actions'; + upgrade.updateType = 'replacement'; + upgrade.pinDigests = true; + upgrade.autoReplaceStringTemplate = + '{{depName}}@{{#if newDigest}}{{newDigest}}{{#if newValue}} # {{newValue}}{{/if}}{{/if}}{{#unless newDigest}}{{newValue}}{{/unless}}'; + upgrade.depName = 'actions/checkout'; + upgrade.currentValue = 'v1.0.0'; + upgrade.currentDigestShort = 'wrong'; + upgrade.depIndex = 0; + upgrade.replaceString = 'actions/checkout@2485f4 # tag=v1.0.0'; + upgrade.newName = 'some-other-action/checkout'; + upgrade.newValue = 'v2.0.0'; + upgrade.newDigest = '1cf887'; + upgrade.packageFile = 'workflow.yml'; + const res = await doAutoReplace( + upgrade, + githubAction, + reuseExistingBranch, + ); + expect(res).toBe( + codeBlock` + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: some-other-action/checkout@2485f4 # tag=v2.0.0 + `, + ); + }); }); }); diff --git a/lib/workers/repository/update/branch/auto-replace.ts b/lib/workers/repository/update/branch/auto-replace.ts index d89862e5daf72c..39bd5204644be5 100644 --- a/lib/workers/repository/update/branch/auto-replace.ts +++ b/lib/workers/repository/update/branch/auto-replace.ts @@ -190,8 +190,10 @@ export async function doAutoReplace( const { packageFile, depName, + depNameTemplate, newName, currentValue, + currentValueTemplate, newValue, currentDigest, currentDigestShort, @@ -237,24 +239,52 @@ export async function doAutoReplace( newString = replaceString!; const autoReplaceRegExpFlag = autoReplaceGlobalMatch ? 'g' : ''; - if (currentValue && newValue) { + if (currentValue && newValue && currentValue !== newValue) { + if (!newString.includes(currentValue)) { + logger.debug( + { stringToReplace: newString, currentValue, currentValueTemplate }, + 'currentValue not found in string to replace', + ); + } newString = newString.replace( regEx(escapeRegExp(currentValue), autoReplaceRegExpFlag), newValue, ); } - if (depName && newName) { + if (depName && newName && depName !== newName) { + if (!newString.includes(depName)) { + logger.debug( + { stringToReplace: newString, depName, depNameTemplate }, + 'depName not found in string to replace', + ); + } newString = newString.replace( regEx(escapeRegExp(depName), autoReplaceRegExpFlag), newName, ); } - if (currentDigest && newDigest) { + if (currentDigest && newDigest && currentDigest !== newDigest) { + if (!newString.includes(currentDigest)) { + logger.debug( + { stringToReplace: newString, currentDigest }, + 'currentDigest not found in string to replace', + ); + } newString = newString.replace( regEx(escapeRegExp(currentDigest), autoReplaceRegExpFlag), newDigest, ); - } else if (currentDigestShort && newDigest) { + } else if ( + currentDigestShort && + newDigest && + currentDigestShort !== newDigest + ) { + if (!newString.includes(currentDigestShort)) { + logger.debug( + { stringToReplace: newString, currentDigestShort }, + 'currentDigestShort not found in string to replace', + ); + } newString = newString.replace( regEx(escapeRegExp(currentDigestShort), autoReplaceRegExpFlag), newDigest, diff --git a/lib/workers/repository/update/branch/execute-post-upgrade-commands.spec.ts b/lib/workers/repository/update/branch/execute-post-upgrade-commands.spec.ts index dd823de3abfa1b..fc5210e2ac4765 100644 --- a/lib/workers/repository/update/branch/execute-post-upgrade-commands.spec.ts +++ b/lib/workers/repository/update/branch/execute-post-upgrade-commands.spec.ts @@ -47,7 +47,7 @@ describe('workers/repository/update/branch/execute-post-upgrade-commands', () => ); GlobalConfig.set({ localDir: __dirname, - allowedPostUpgradeCommands: ['some-command'], + allowedCommands: ['some-command'], }); fs.localPathIsFile .mockResolvedValueOnce(true) @@ -97,7 +97,7 @@ describe('workers/repository/update/branch/execute-post-upgrade-commands', () => ); GlobalConfig.set({ localDir: __dirname, - allowedPostUpgradeCommands: ['some-command'], + allowedCommands: ['some-command'], }); fs.localPathIsFile .mockResolvedValueOnce(true) @@ -146,7 +146,7 @@ describe('workers/repository/update/branch/execute-post-upgrade-commands', () => ); GlobalConfig.set({ localDir: __dirname, - allowedPostUpgradeCommands: ['some-command'], + allowedCommands: ['some-command'], }); fs.localPathIsFile .mockResolvedValueOnce(true) diff --git a/lib/workers/repository/update/branch/execute-post-upgrade-commands.ts b/lib/workers/repository/update/branch/execute-post-upgrade-commands.ts index b295198c37036a..d934ae424887a7 100644 --- a/lib/workers/repository/update/branch/execute-post-upgrade-commands.ts +++ b/lib/workers/repository/update/branch/execute-post-upgrade-commands.ts @@ -30,19 +30,15 @@ export async function postUpgradeCommandsExecutor( ): Promise { let updatedArtifacts = [...(config.updatedArtifacts ?? [])]; const artifactErrors = [...(config.artifactErrors ?? [])]; - const allowedPostUpgradeCommands = GlobalConfig.get( - 'allowedPostUpgradeCommands', - ); - const allowPostUpgradeCommandTemplating = GlobalConfig.get( - 'allowPostUpgradeCommandTemplating', - ); + const allowedCommands = GlobalConfig.get('allowedCommands'); + const allowCommandTemplating = GlobalConfig.get('allowCommandTemplating'); for (const upgrade of filteredUpgradeCommands) { addMeta({ dep: upgrade.depName }); logger.trace( { tasks: upgrade.postUpgradeTasks, - allowedCommands: allowedPostUpgradeCommands, + allowedCommands, }, `Checking for post-upgrade tasks`, ); @@ -65,13 +61,9 @@ export async function postUpgradeCommandsExecutor( } for (const cmd of commands) { - if ( - allowedPostUpgradeCommands!.some((pattern) => - regEx(pattern).test(cmd), - ) - ) { + if (allowedCommands!.some((pattern) => regEx(pattern).test(cmd))) { try { - const compiledCmd = allowPostUpgradeCommandTemplating + const compiledCmd = allowCommandTemplating ? compile(cmd, mergeChildConfig(config, upgrade)) : cmd; @@ -94,14 +86,14 @@ export async function postUpgradeCommandsExecutor( logger.warn( { cmd, - allowedPostUpgradeCommands, + allowedCommands, }, - 'Post-upgrade task did not match any on allowedPostUpgradeCommands list', + 'Post-upgrade task did not match any on allowedCommands list', ); artifactErrors.push({ lockFile: upgrade.packageFile, stderr: sanitize( - `Post-upgrade command '${cmd}' has not been added to the allowed list in allowedPostUpgradeCommands`, + `Post-upgrade command '${cmd}' has not been added to the allowed list in allowedCommands`, ), }); } diff --git a/lib/workers/repository/update/branch/get-updated.ts b/lib/workers/repository/update/branch/get-updated.ts index 59bdac1e429a12..2c69a82fdbd640 100644 --- a/lib/workers/repository/update/branch/get-updated.ts +++ b/lib/workers/repository/update/branch/get-updated.ts @@ -14,7 +14,7 @@ import type { import { getFile } from '../../../../util/git'; import type { FileAddition, FileChange } from '../../../../util/git/types'; import { coerceString } from '../../../../util/string'; -import type { BranchConfig } from '../../../types'; +import type { BranchConfig, BranchUpgradeConfig } from '../../../types'; import { doAutoReplace } from './auto-replace'; export interface PackageFilesResult { @@ -231,7 +231,6 @@ export async function getUpdatedPackageFiles( } } } else { - const bumpPackageVersion = get(manager, 'bumpPackageVersion'); const updateDependency = get(manager, 'updateDependency'); if (!updateDependency) { let res = await doAutoReplace( @@ -242,19 +241,7 @@ export async function getUpdatedPackageFiles( ); firstUpdate = false; if (res) { - if ( - bumpPackageVersion && - upgrade.bumpVersion && - upgrade.packageFileVersion - ) { - const { bumpedContent } = await bumpPackageVersion( - res, - upgrade.packageFileVersion, - upgrade.bumpVersion, - packageFile, - ); - res = bumpedContent; - } + res = await applyManagerBumpPackageVersion(res, upgrade); if (res === packageFileContent) { logger.debug({ packageFile, depName }, 'No content changed'); } else { @@ -276,20 +263,7 @@ export async function getUpdatedPackageFiles( fileContent: packageFileContent!, upgrade, }); - if ( - newContent && - bumpPackageVersion && - upgrade.bumpVersion && - upgrade.packageFileVersion - ) { - const { bumpedContent } = await bumpPackageVersion( - newContent, - upgrade.packageFileVersion, - upgrade.bumpVersion, - packageFile, - ); - newContent = bumpedContent; - } + newContent = await applyManagerBumpPackageVersion(newContent, upgrade); if (!newContent) { if (reuseExistingBranch) { logger.debug( @@ -530,3 +504,27 @@ function processUpdateArtifactResults( } } } + +async function applyManagerBumpPackageVersion( + packageFileContent: string | null, + upgrade: BranchUpgradeConfig, +): Promise { + const bumpPackageVersion = get(upgrade.manager, 'bumpPackageVersion'); + if ( + !bumpPackageVersion || + !packageFileContent || + !upgrade.bumpVersion || + !upgrade.packageFileVersion + ) { + return packageFileContent; + } + + const result = await bumpPackageVersion( + packageFileContent, + upgrade.packageFileVersion, + upgrade.bumpVersion, + upgrade.packageFile!, + ); + + return result.bumpedContent; +} diff --git a/lib/workers/repository/update/branch/index.spec.ts b/lib/workers/repository/update/branch/index.spec.ts index ba8aba7d85a76f..feddb9d5c33c4c 100644 --- a/lib/workers/repository/update/branch/index.spec.ts +++ b/lib/workers/repository/update/branch/index.spec.ts @@ -36,6 +36,7 @@ import type { } from '../../../../util/git/types'; import * as _mergeConfidence from '../../../../util/merge-confidence'; import * as _sanitize from '../../../../util/sanitize'; +import type { Timestamp } from '../../../../util/timestamp'; import * as _limits from '../../../global/limits'; import type { BranchConfig, BranchUpgradeConfig } from '../../../types'; import type { ResultWithPr } from '../pr'; @@ -183,11 +184,13 @@ describe('workers/repository/update/branch/index', () => { config.prCreation = 'not-pending'; (config.upgrades as Partial[]) = [ { - releaseTimestamp: new Date('2019-01-01').getTime().toString(), + releaseTimestamp: new Date('2019-01-01') + .getTime() + .toString() as Timestamp, minimumReleaseAge: '1 day', }, { - releaseTimestamp: new Date().toString(), + releaseTimestamp: new Date().toString() as Timestamp, minimumReleaseAge: '1 day', }, ]; @@ -206,7 +209,7 @@ describe('workers/repository/update/branch/index', () => { config.prCreation = 'not-pending'; config.upgrades = partial([ { - releaseTimestamp: '2099-12-31', + releaseTimestamp: '2099-12-31' as Timestamp, minimumReleaseAge: '1 day', }, ]); @@ -1092,7 +1095,7 @@ describe('workers/repository/update/branch/index', () => { pr: partial(), }); prAutomerge.checkAutoMerge.mockResolvedValueOnce({ automerged: true }); - config.releaseTimestamp = '2018-04-26T05:15:51.877Z'; + config.releaseTimestamp = '2018-04-26T05:15:51.877Z' as Timestamp; commit.commitFilesToBranch.mockResolvedValueOnce(null); await branchWorker.processBranch(config); expect(platform.ensureComment).toHaveBeenCalledTimes(1); @@ -1117,7 +1120,7 @@ describe('workers/repository/update/branch/index', () => { pr: partial(), }); prAutomerge.checkAutoMerge.mockResolvedValueOnce({ automerged: true }); - config.releaseTimestamp = new Date().toISOString(); + config.releaseTimestamp = new Date().toISOString() as Timestamp; commit.commitFilesToBranch.mockResolvedValueOnce(null); await branchWorker.processBranch(config); expect(platform.ensureComment).toHaveBeenCalledTimes(1); @@ -1142,7 +1145,7 @@ describe('workers/repository/update/branch/index', () => { pr: partial(), }); prAutomerge.checkAutoMerge.mockResolvedValueOnce({ automerged: true }); - config.releaseTimestamp = new Date().toISOString(); + config.releaseTimestamp = new Date().toISOString() as Timestamp; await expect(branchWorker.processBranch(config)).rejects.toThrow( Error(MANAGER_LOCKFILE_ERROR), ); @@ -1631,8 +1634,8 @@ describe('workers/repository/update/branch/index', () => { GlobalConfig.set({ ...adminConfig, - allowedPostUpgradeCommands: ['^echo {{{versioning}}}$'], - allowPostUpgradeCommandTemplating: true, + allowedCommands: ['^echo {{{versioning}}}$'], + allowCommandTemplating: true, exposeAllEnv: true, localDir: '/localDir', }); @@ -1665,7 +1668,7 @@ describe('workers/repository/update/branch/index', () => { commitSha: null, }); const errorMessage = expect.stringContaining( - "Post-upgrade command 'disallowed task' has not been added to the allowed list in allowedPostUpgradeCommand", + "Post-upgrade command 'disallowed task' has not been added to the allowed list in allowedCommands", ); expect(platform.ensureComment).toHaveBeenCalledWith( expect.objectContaining({ @@ -1729,8 +1732,8 @@ describe('workers/repository/update/branch/index', () => { GlobalConfig.set({ ...adminConfig, - allowedPostUpgradeCommands: ['^exit 1$'], - allowPostUpgradeCommandTemplating: true, + allowedCommands: ['^exit 1$'], + allowCommandTemplating: true, exposeAllEnv: true, localDir: '/localDir', }); @@ -1814,8 +1817,8 @@ describe('workers/repository/update/branch/index', () => { commit.commitFilesToBranch.mockResolvedValueOnce(null); GlobalConfig.set({ ...adminConfig, - allowedPostUpgradeCommands: ['^echo {{{versioning}}}$'], - allowPostUpgradeCommandTemplating: false, + allowedCommands: ['^echo {{{versioning}}}$'], + allowCommandTemplating: false, exposeAllEnv: true, localDir: '/localDir', }); @@ -1916,8 +1919,8 @@ describe('workers/repository/update/branch/index', () => { GlobalConfig.set({ ...adminConfig, - allowedPostUpgradeCommands: ['^echo {{{depName}}}$'], - allowPostUpgradeCommandTemplating: true, + allowedCommands: ['^echo {{{depName}}}$'], + allowCommandTemplating: true, exposeAllEnv: true, localDir: '/localDir', }); @@ -2066,8 +2069,8 @@ describe('workers/repository/update/branch/index', () => { GlobalConfig.set({ ...adminConfig, - allowedPostUpgradeCommands: ['^echo hardcoded-string$'], - allowPostUpgradeCommandTemplating: true, + allowedCommands: ['^echo hardcoded-string$'], + allowCommandTemplating: true, trustLevel: 'high', localDir: '/localDir', }); diff --git a/lib/workers/repository/update/branch/index.ts b/lib/workers/repository/update/branch/index.ts index e76b4c2dce533d..b6038b5ba94399 100644 --- a/lib/workers/repository/update/branch/index.ts +++ b/lib/workers/repository/update/branch/index.ts @@ -34,7 +34,7 @@ import { import { coerceNumber } from '../../../../util/number'; import { toMs } from '../../../../util/pretty-time'; import * as template from '../../../../util/template'; -import { isLimitReached } from '../../../global/limits'; +import { getCount, isLimitReached } from '../../../global/limits'; import type { BranchConfig, BranchResult, PrBlockedBy } from '../../../types'; import { embedChangelogs } from '../../changelog'; import { ensurePr, getPlatformPrOptions } from '../pr'; @@ -212,9 +212,14 @@ export async function processBranch( }; } } + + logger.debug( + `Open PR Count: ${getCount('ConcurrentPRs')}, Existing Branch Count: ${getCount('Branches')}, Hourly PR Count: ${getCount('HourlyPRs')}`, + ); + if ( !branchExists && - isLimitReached('Branches') && + isLimitReached('Branches', branchConfig) && !dependencyDashboardCheck && !config.isVulnerabilityAlert ) { diff --git a/lib/workers/repository/update/branch/schedule.ts b/lib/workers/repository/update/branch/schedule.ts index 7789ebcc4c0e66..aa85a8ede739bc 100644 --- a/lib/workers/repository/update/branch/schedule.ts +++ b/lib/workers/repository/update/branch/schedule.ts @@ -108,7 +108,10 @@ export function cronMatches( const nextRun = parsedCron.nextRun(); // istanbul ignore if: should not happen if (!nextRun) { - logger.warn(`Invalid cron schedule ${cron}. No next run is possible`); + logger.warn( + { schedule: cron }, + 'Invalid cron schedule. No next run is possible', + ); return false; } @@ -144,7 +147,8 @@ export function isScheduledNow( } if (!is.array(configSchedule)) { logger.warn( - `config schedule is not an array: ${JSON.stringify(configSchedule)}`, + { schedule: configSchedule }, + 'config schedule is not an array', ); configSchedule = [configSchedule]; } diff --git a/lib/workers/repository/update/pr/changelog/gitea/index.spec.ts b/lib/workers/repository/update/pr/changelog/gitea/index.spec.ts index 8bf9593052931d..b00012eccfe0e1 100644 --- a/lib/workers/repository/update/pr/changelog/gitea/index.spec.ts +++ b/lib/workers/repository/update/pr/changelog/gitea/index.spec.ts @@ -4,6 +4,7 @@ import { partial } from '../../../../../../../test/util'; import * as semverVersioning from '../../../../../../modules/versioning/semver'; import * as hostRules from '../../../../../../util/host-rules'; import { toBase64 } from '../../../../../../util/string'; +import type { Timestamp } from '../../../../../../util/timestamp'; import type { BranchUpgradeConfig } from '../../../../../types'; import { GiteaChangeLogSource } from '../gitea/source'; import { getReleaseNotesMd } from '.'; @@ -22,10 +23,13 @@ const upgrade = partial({ { version: '5.2.0' }, { version: '5.4.0', - releaseTimestamp: '2018-08-24T14:23:00.000Z', + releaseTimestamp: '2018-08-24T14:23:00.000Z' as Timestamp, }, { version: '5.5.0', gitRef: 'eba303e91c930292198b2fc57040145682162a1b' }, - { version: '5.6.0', releaseTimestamp: '2020-02-13T15:37:00.000Z' }, + { + version: '5.6.0', + releaseTimestamp: '2020-02-13T15:37:00.000Z' as Timestamp, + }, { version: '5.6.1' }, ], }); diff --git a/lib/workers/repository/update/pr/changelog/github/index.spec.ts b/lib/workers/repository/update/pr/changelog/github/index.spec.ts index 4e85b2a379b4af..b5a3523ca1098e 100644 --- a/lib/workers/repository/update/pr/changelog/github/index.spec.ts +++ b/lib/workers/repository/update/pr/changelog/github/index.spec.ts @@ -6,6 +6,7 @@ import * as semverVersioning from '../../../../../../modules/versioning/semver'; import * as githubGraphql from '../../../../../../util/github/graphql'; import type { GithubTagItem } from '../../../../../../util/github/graphql/types'; import * as hostRules from '../../../../../../util/host-rules'; +import type { Timestamp } from '../../../../../../util/timestamp'; import type { BranchUpgradeConfig } from '../../../../../types'; jest.mock('../../../../../../modules/datasource/npm'); @@ -25,10 +26,13 @@ const upgrade = partial({ { version: '2.3.0', gitRef: 'npm_2.3.0', - releaseTimestamp: '2017-10-24T03:20:46.238Z', + releaseTimestamp: '2017-10-24T03:20:46.238Z' as Timestamp, }, { version: '2.2.2', gitRef: 'npm_2.2.2' }, - { version: '2.4.2', releaseTimestamp: '2017-12-24T03:20:46.238Z' }, + { + version: '2.4.2', + releaseTimestamp: '2017-12-24T03:20:46.238Z' as Timestamp, + }, { version: '2.5.2' }, ], }); diff --git a/lib/workers/repository/update/pr/changelog/github/index.ts b/lib/workers/repository/update/pr/changelog/github/index.ts index 89e061532d9252..64d21bd9e3f7c4 100644 --- a/lib/workers/repository/update/pr/changelog/github/index.ts +++ b/lib/workers/repository/update/pr/changelog/github/index.ts @@ -28,11 +28,11 @@ export async function getReleaseNotesMd( logger.trace('github.getReleaseNotesMd()'); const apiPrefix = `${ensureTrailingSlash(apiBaseUrl)}repos/${repository}`; const { default_branch: defaultBranch = 'HEAD' } = ( - await http.getJson<{ default_branch: string }>(apiPrefix) + await http.getJsonUnchecked<{ default_branch: string }>(apiPrefix) ).body; // https://docs.github.com/en/rest/reference/git#get-a-tree - const res = await http.getJson( + const res = await http.getJsonUnchecked( `${apiPrefix}/git/trees/${defaultBranch}${ sourceDirectory ? '?recursive=1' : '' }`, @@ -73,7 +73,7 @@ export async function getReleaseNotesMd( } // https://docs.github.com/en/rest/reference/git#get-a-blob - const fileRes = await http.getJson( + const fileRes = await http.getJsonUnchecked( `${apiPrefix}/git/blobs/${sha}`, ); diff --git a/lib/workers/repository/update/pr/changelog/gitlab/index.spec.ts b/lib/workers/repository/update/pr/changelog/gitlab/index.spec.ts index f55e0e197cfc39..8986b7dbbb081b 100644 --- a/lib/workers/repository/update/pr/changelog/gitlab/index.spec.ts +++ b/lib/workers/repository/update/pr/changelog/gitlab/index.spec.ts @@ -3,6 +3,7 @@ import * as httpMock from '../../../../../../../test/http-mock'; import { partial } from '../../../../../../../test/util'; import * as semverVersioning from '../../../../../../modules/versioning/semver'; import * as hostRules from '../../../../../../util/host-rules'; +import type { Timestamp } from '../../../../../../util/timestamp'; import type { BranchUpgradeConfig } from '../../../../../types'; import { GitLabChangeLogSource } from './source'; @@ -20,10 +21,13 @@ const upgrade = partial({ { version: '5.2.0' }, { version: '5.4.0', - releaseTimestamp: '2018-08-24T14:23:00.000Z', + releaseTimestamp: '2018-08-24T14:23:00.000Z' as Timestamp, }, { version: '5.5.0', gitRef: 'eba303e91c930292198b2fc57040145682162a1b' }, - { version: '5.6.0', releaseTimestamp: '2020-02-13T15:37:00.000Z' }, + { + version: '5.6.0', + releaseTimestamp: '2020-02-13T15:37:00.000Z' as Timestamp, + }, { version: '5.6.1' }, ], }); diff --git a/lib/workers/repository/update/pr/changelog/gitlab/index.ts b/lib/workers/repository/update/pr/changelog/gitlab/index.ts index 8957d154ea81a0..4c17d4bac34fa5 100644 --- a/lib/workers/repository/update/pr/changelog/gitlab/index.ts +++ b/lib/workers/repository/update/pr/changelog/gitlab/index.ts @@ -25,7 +25,7 @@ export async function getReleaseNotesMd( // https://docs.gitlab.com/13.2/ee/api/repositories.html#list-repository-tree const tree = ( - await http.getJson( + await http.getJsonUnchecked( `${apiPrefix}tree?per_page=100${ sourceDirectory ? `&path=${sourceDirectory}` : '' }`, @@ -69,9 +69,12 @@ export async function getReleaseList( const urlEncodedRepo = encodeURIComponent(repository); const apiUrl = `${apiBaseUrl}projects/${urlEncodedRepo}/releases`; - const res = await http.getJson(`${apiUrl}?per_page=100`, { - paginate: true, - }); + const res = await http.getJsonUnchecked( + `${apiUrl}?per_page=100`, + { + paginate: true, + }, + ); return res.body.map((release) => ({ url: `${project.baseUrl}${repository}/-/releases/${release.tag_name}`, notesSourceUrl: apiUrl, diff --git a/lib/workers/repository/update/pr/changelog/index.spec.ts b/lib/workers/repository/update/pr/changelog/index.spec.ts index 5dcfa186fcc60c..f74d1cbc1a9f0a 100644 --- a/lib/workers/repository/update/pr/changelog/index.spec.ts +++ b/lib/workers/repository/update/pr/changelog/index.spec.ts @@ -4,6 +4,7 @@ import { GlobalConfig } from '../../../../../config/global'; import * as semverVersioning from '../../../../../modules/versioning/semver'; import * as githubGraphql from '../../../../../util/github/graphql'; import * as hostRules from '../../../../../util/host-rules'; +import type { Timestamp } from '../../../../../util/timestamp'; import type { BranchConfig } from '../../../../types'; import * as releases from './releases'; import { getChangeLogJSON } from '.'; @@ -29,10 +30,13 @@ const upgrade = partial({ { version: '2.3.0', gitRef: 'npm_2.3.0', - releaseTimestamp: '2017-10-24T03:20:46.238Z', + releaseTimestamp: '2017-10-24T03:20:46.238Z' as Timestamp, }, { version: '2.2.2', gitRef: 'npm_2.2.2' }, - { version: '2.4.2', releaseTimestamp: '2017-12-24T03:20:46.238Z' }, + { + version: '2.4.2', + releaseTimestamp: '2017-12-24T03:20:46.238Z' as Timestamp, + }, { version: '2.5.2' }, ], }); diff --git a/lib/workers/repository/update/pr/changelog/release-notes.spec.ts b/lib/workers/repository/update/pr/changelog/release-notes.spec.ts index 7bb3bd2ebb0c77..42534bb5bc1e08 100644 --- a/lib/workers/repository/update/pr/changelog/release-notes.spec.ts +++ b/lib/workers/repository/update/pr/changelog/release-notes.spec.ts @@ -8,6 +8,7 @@ import * as githubGraphql from '../../../../../util/github/graphql'; import type { GithubReleaseItem } from '../../../../../util/github/graphql/types'; import * as _hostRules from '../../../../../util/host-rules'; import { toBase64 } from '../../../../../util/string'; +import type { Timestamp } from '../../../../../util/timestamp'; import type { BranchUpgradeConfig } from '../../../../types'; import { addReleaseNotes, @@ -251,7 +252,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { githubReleasesMock.mockResolvedValueOnce([ { version: `v1.0.0`, - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, id: 1, url: 'https://example.com', name: 'some/dep', @@ -259,7 +260,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { }, { version: `v1.0.1`, - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, id: 1, url: 'https://example.com', name: 'some/dep', @@ -370,7 +371,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { githubReleasesMock.mockResolvedValueOnce([ { version: '1.0.0', - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, id: 1, url: 'https://github.com/some/other-repository/releases/1.0.0', name: '', @@ -378,7 +379,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { }, { version: '1.0.1', - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, id: 2, url: 'https://github.com/some/other-repository/releases/1.0.1', name: '', @@ -404,7 +405,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { githubReleasesMock.mockResolvedValueOnce([ { version: '1.0.0', - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, id: 1, url: 'https://github.com/some/other-repository/releases/1.0.0', name: 'some/dep', @@ -412,7 +413,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { }, { version: '1.0.1', - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, id: 2, url: 'https://github.com/some/other-repository/releases/1.0.1', name: 'some/dep', @@ -447,7 +448,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { githubReleasesMock.mockResolvedValueOnce([ { version: '1.0.0', - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, id: 1, url: 'https://github.com/some/other-repository/releases/1.0.0', name: 'some/dep', @@ -455,7 +456,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { }, { version: '1.0.1', - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, id: 2, url: 'https://github.com/some/other-repository/releases/1.0.1', name: 'some release name', @@ -489,7 +490,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { githubReleasesMock.mockResolvedValueOnce([ { version: '1.0.0', - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, id: 1, url: 'https://github.com/some/other-repository/releases/1.0.0', name: 'Release v1.0.0', @@ -497,7 +498,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { }, { version: '1.0.1', - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, id: 2, url: 'https://github.com/some/other-repository/releases/1.0.1', name: '1.0.1', @@ -531,7 +532,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { githubReleasesMock.mockResolvedValueOnce([ { version: '1.0.0', - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, id: 1, url: 'https://github.com/some/other-repository/releases/1.0.1', name: 'some/dep', @@ -539,7 +540,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { }, { version: '1.0.1', - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, id: 2, url: 'https://github.com/some/other-repository/releases/1.0.1', name: 'v1.0.1 some release', @@ -573,7 +574,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { githubReleasesMock.mockResolvedValueOnce([ { version: '1.0.0', - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, id: 1, url: 'correct/url/tag.com', name: 'some/dep', @@ -581,7 +582,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { }, { version: '1.0.1', - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, id: 2, url: 'correct/url/tag.com', name: '1.0.1', @@ -607,7 +608,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { githubReleasesMock.mockResolvedValueOnce([ { version: 'v1.0.0', - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, id: 1, url: 'https://github.com/some/other-repository/releases/v1.0.0', name: 'some/dep', @@ -615,7 +616,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { }, { version: 'v1.0.1', - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, id: 2, url: 'https://github.com/some/other-repository/releases/v1.0.1', name: 'some/dep', @@ -650,7 +651,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { githubReleasesMock.mockResolvedValueOnce([ { version: 'other-1.0.0', - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, id: 1, url: 'https://github.com/some/other-repository/releases/other-1.0.0', name: 'some/dep', @@ -658,7 +659,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { }, { version: 'other-1.0.1', - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, id: 2, url: 'https://github.com/some/other-repository/releases/other-1.0.1', name: 'some/dep', @@ -694,7 +695,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { githubReleasesMock.mockResolvedValueOnce([ { version: 'other-1.0.0', - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, id: 1, url: 'https://github.com/some/other-repository/releases/other-1.0.0', name: 'some/dep', @@ -702,7 +703,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { }, { version: 'other-1.0.1', - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, id: 2, url: 'https://github.com/some/other-repository/releases/other-1.0.1', name: 'some/dep', @@ -739,7 +740,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { githubReleasesMock.mockResolvedValueOnce([ { version: 'other_v1.0.0', - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, id: 1, url: 'https://github.com/some/other-repository/releases/other_v1.0.0', name: 'some/dep', @@ -747,7 +748,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { }, { version: 'other_v1.0.1', - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, id: 2, url: 'https://github.com/some/other-repository/releases/other_v1.0.1', name: 'some/dep', @@ -784,7 +785,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { { version: 'other@1.0.0', id: 1, - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, url: 'https://github.com/some/other-repository/releases/other@1.0.0', name: 'some/dep', description: 'some body', @@ -794,7 +795,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { description: 'some body #123, [#124](https://github.com/some/yet-other-repository/issues/124)', id: 2, - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, url: 'https://github.com/some/other-repository/releases/other@1.0.1', name: 'some/dep', }, @@ -956,7 +957,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { { id: 1, version: `${packageName}@1.0.0`, - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, url: 'correct/url/tag.com', name: 'some/dep', description: 'some body', @@ -964,7 +965,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { { id: 2, version: `someOtherRelease1/exampleDep_1.0.0`, - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, url: 'correct/url/tag.com', name: 'some/dep', description: 'some body', @@ -972,7 +973,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { { id: 3, version: `someOtherRelease2/exampleDep-1.0.0`, - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, url: 'correct/url/tag.com', name: 'some/dep', description: 'some body', @@ -1006,7 +1007,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { { id: 123, version: `app-1.0.0`, - releaseTimestamp: '2020-01-01', + releaseTimestamp: '2020-01-01' as Timestamp, url: 'correct/url/tag.com', description: 'some body', name: 'some/dep', diff --git a/lib/workers/repository/update/pr/index.spec.ts b/lib/workers/repository/update/pr/index.spec.ts index c0a8f6ac090895..20de0385e341a8 100644 --- a/lib/workers/repository/update/pr/index.spec.ts +++ b/lib/workers/repository/update/pr/index.spec.ts @@ -89,8 +89,9 @@ describe('workers/repository/update/pr/index', () => { const res = await ensurePr(config); expect(res).toEqual({ type: 'with-pr', pr }); - expect(limits.incLimitedValue).toHaveBeenCalledOnce(); - expect(limits.incLimitedValue).toHaveBeenCalledWith('PullRequests'); + expect(limits.incCountValue).toHaveBeenCalledTimes(2); + expect(limits.incCountValue).toHaveBeenCalledWith('ConcurrentPRs'); + expect(limits.incCountValue).toHaveBeenCalledWith('HourlyPRs'); expect(logger.logger.info).toHaveBeenCalledWith( { pr: pr.number, prTitle }, 'PR created', @@ -730,9 +731,9 @@ describe('workers/repository/update/pr/index', () => { assignAutomerge: false, }); - expect(logger.logger.error).toHaveBeenCalledWith( - { err }, - 'Failed to ensure PR: ' + prTitle, + expect(logger.logger.warn).toHaveBeenCalledWith( + { err, prTitle }, + 'Failed to ensure PR', ); }); diff --git a/lib/workers/repository/update/pr/index.ts b/lib/workers/repository/update/pr/index.ts index edc85177054180..6b1a05a75e0af9 100644 --- a/lib/workers/repository/update/pr/index.ts +++ b/lib/workers/repository/update/pr/index.ts @@ -27,7 +27,7 @@ import { stripEmojis } from '../../../../util/emoji'; import { fingerprint } from '../../../../util/fingerprint'; import { getBranchLastCommitTime } from '../../../../util/git'; import { memoize } from '../../../../util/memoize'; -import { incLimitedValue, isLimitReached } from '../../../global/limits'; +import { incCountValue, isLimitReached } from '../../../global/limits'; import type { BranchConfig, BranchUpgradeConfig, @@ -482,7 +482,7 @@ export async function ensurePr( try { if ( !dependencyDashboardCheck && - isLimitReached('PullRequests') && + isLimitReached('ConcurrentPRs', prConfig) && !config.isVulnerabilityAlert ) { logger.debug('Skipping PR - limit reached'); @@ -499,7 +499,8 @@ export async function ensurePr( milestone: config.milestone, }); - incLimitedValue('PullRequests'); + incCountValue('ConcurrentPRs'); + incCountValue('HourlyPRs'); logger.info({ pr: pr?.number, prTitle }, 'PR created'); } catch (err) { logger.debug({ err }, 'Pull request creation error'); @@ -573,7 +574,7 @@ export async function ensurePr( logger.debug('Passing error up'); throw err; } - logger.error({ err }, 'Failed to ensure PR: ' + prTitle); + logger.warn({ err, prTitle }, 'Failed to ensure PR'); } if (existingPr) { return { type: 'with-pr', pr: existingPr }; diff --git a/lib/workers/repository/updates/branch-name.spec.ts b/lib/workers/repository/updates/branch-name.spec.ts index a4b4dd5993954e..5feb61bb8e0e57 100644 --- a/lib/workers/repository/updates/branch-name.spec.ts +++ b/lib/workers/repository/updates/branch-name.spec.ts @@ -3,9 +3,22 @@ import { generateBranchName } from './branch-name'; describe('workers/repository/updates/branch-name', () => { describe('getBranchName()', () => { - it('uses groupName if no slug defined', () => { + it('falls back to sharedVariableName if no groupName', () => { + const upgrade: RenovateConfig = { + sharedVariableName: 'some variable name', + group: { + branchName: '{{groupSlug}}-{{branchTopic}}', + branchTopic: 'grouptopic', + }, + }; + generateBranchName(upgrade); + expect(upgrade.branchName).toBe('some-variable-name-grouptopic'); + }); + + it('uses groupName if no slug defined, ignores sharedVariableName', () => { const upgrade: RenovateConfig = { groupName: 'some group name', + sharedVariableName: 'some variable name', group: { branchName: '{{groupSlug}}-{{branchTopic}}', branchTopic: 'grouptopic', diff --git a/lib/workers/repository/updates/branch-name.ts b/lib/workers/repository/updates/branch-name.ts index d151c0772b8c24..36f085dd1fe08d 100644 --- a/lib/workers/repository/updates/branch-name.ts +++ b/lib/workers/repository/updates/branch-name.ts @@ -57,6 +57,12 @@ 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.sharedVariableName) { + logger.debug( + `Using sharedVariableName=${update.sharedVariableName} as groupName for depName=${update.depName}`, + ); + update.groupName = update.sharedVariableName; + } if (update.groupName) { update.groupName = template.compile(update.groupName, update); logger.trace('Using group branchName template'); diff --git a/lib/workers/repository/updates/flatten.spec.ts b/lib/workers/repository/updates/flatten.spec.ts index 42cd3a7b3b6fe7..8561d1a0a1afb0 100644 --- a/lib/workers/repository/updates/flatten.spec.ts +++ b/lib/workers/repository/updates/flatten.spec.ts @@ -1,7 +1,7 @@ import is from '@sindresorhus/is'; import type { RenovateConfig } from '../../../../test/util'; import { getConfig } from '../../../config/defaults'; -import { flattenUpdates } from './flatten'; +import { flattenUpdates, sanitizeDepName } from './flatten'; jest.mock('../../../util/git/semantic'); @@ -14,6 +14,14 @@ beforeEach(() => { }); describe('workers/repository/updates/flatten', () => { + describe('sanitizeDepName()', () => { + it('sanitizes urls', () => { + expect(sanitizeDepName('https://some.host.name/a/path/to.git')).toBe( + 'https-some.host.name-a-path-to.git', + ); + }); + }); + describe('flattenUpdates()', () => { it('flattens', async () => { // TODO #22198 diff --git a/lib/workers/repository/updates/flatten.ts b/lib/workers/repository/updates/flatten.ts index 93b1d51c8556c4..989ff00553864c 100644 --- a/lib/workers/repository/updates/flatten.ts +++ b/lib/workers/repository/updates/flatten.ts @@ -17,12 +17,13 @@ import { generateBranchName } from './branch-name'; const upper = (str: string): string => str.charAt(0).toUpperCase() + str.substring(1); -function sanitizeDepName(depName: string): string { +export function sanitizeDepName(depName: string): string { return depName .replace('@types/', '') .replace('@', '') .replace(regEx(/\//g), '-') .replace(regEx(/\s+/g), '-') + .replace(regEx(/:/g), '-') .replace(regEx(/-+/), '-') .toLowerCase(); } diff --git a/lib/workers/repository/updates/generate.spec.ts b/lib/workers/repository/updates/generate.spec.ts index 6769da86bb6803..ad7bfe4f862801 100644 --- a/lib/workers/repository/updates/generate.spec.ts +++ b/lib/workers/repository/updates/generate.spec.ts @@ -2,6 +2,7 @@ import { codeBlock } from 'common-tags'; import { getConfig } from '../../../config/defaults'; import type { UpdateType } from '../../../config/types'; import { NpmDatasource } from '../../../modules/datasource/npm'; +import type { Timestamp } from '../../../util/timestamp'; import type { BranchUpgradeConfig } from '../../types'; import { generateBranchConfig } from './generate'; @@ -34,7 +35,7 @@ describe('workers/repository/updates/generate', () => { depName: 'some-dep', groupName: 'some-group', prTitle: 'some-title', - releaseTimestamp: '2017-02-07T20:01:41+00:00', + releaseTimestamp: '2017-02-07T20:01:41+00:00' as Timestamp, foo: 1, group: { foo: 2, @@ -161,7 +162,7 @@ describe('workers/repository/updates/generate', () => { group: { foo: 2, }, - releaseTimestamp: '2017-02-07T20:01:41+00:00', + releaseTimestamp: '2017-02-07T20:01:41+00:00' as Timestamp, automerge: true, constraints: { foo: '1.0.0', @@ -181,7 +182,7 @@ describe('workers/repository/updates/generate', () => { group: { foo: 2, }, - releaseTimestamp: '2017-02-06T20:01:41+00:00', + releaseTimestamp: '2017-02-06T20:01:41+00:00' as Timestamp, automerge: false, constraints: { foo: '1.0.0', @@ -202,7 +203,7 @@ describe('workers/repository/updates/generate', () => { group: { foo: 2, }, - releaseTimestamp: '2017-02-06T20:01:41+00:00', + releaseTimestamp: '2017-02-06T20:01:41+00:00' as Timestamp, automerge: false, }, ]; @@ -400,7 +401,7 @@ describe('workers/repository/updates/generate', () => { group: { foo: 2, }, - releaseTimestamp: '2017-02-07T20:01:41+00:00', + releaseTimestamp: '2017-02-07T20:01:41+00:00' as Timestamp, updateType: 'minor', separateMinorPatch: true, prTitleStrict: true, @@ -420,7 +421,7 @@ describe('workers/repository/updates/generate', () => { group: { foo: 2, }, - releaseTimestamp: '2017-02-08T20:01:41+00:00', + releaseTimestamp: '2017-02-08T20:01:41+00:00' as Timestamp, updateType: 'minor', separateMinorPatch: true, prTitleStrict: true, @@ -455,7 +456,7 @@ describe('workers/repository/updates/generate', () => { group: { foo: 2, }, - releaseTimestamp: '2017-02-07T20:01:41+00:00', + releaseTimestamp: '2017-02-07T20:01:41+00:00' as Timestamp, updateType: 'minor', separateMinorPatch: true, }, @@ -474,7 +475,7 @@ describe('workers/repository/updates/generate', () => { group: { foo: 2, }, - releaseTimestamp: '2017-02-08T20:01:41+00:00', + releaseTimestamp: '2017-02-08T20:01:41+00:00' as Timestamp, updateType: 'minor', separateMinorPatch: true, }, @@ -507,7 +508,7 @@ describe('workers/repository/updates/generate', () => { group: { foo: 2, }, - releaseTimestamp: '2017-02-07T20:01:41+00:00', + releaseTimestamp: '2017-02-07T20:01:41+00:00' as Timestamp, }, { manager: 'some-manager', @@ -523,7 +524,7 @@ describe('workers/repository/updates/generate', () => { group: { foo: 2, }, - releaseTimestamp: '2017-02-08T20:01:41+00:00', + releaseTimestamp: '2017-02-08T20:01:41+00:00' as Timestamp, }, ] satisfies BranchUpgradeConfig[]; const res = generateBranchConfig(branch); @@ -550,7 +551,7 @@ describe('workers/repository/updates/generate', () => { group: { foo: 2, }, - releaseTimestamp: '2017-02-07T20:01:41+00:00', + releaseTimestamp: '2017-02-07T20:01:41+00:00' as Timestamp, }, { manager: 'some-manager', @@ -566,7 +567,7 @@ describe('workers/repository/updates/generate', () => { group: { foo: 2, }, - releaseTimestamp: '2017-02-08T20:01:41+00:00', + releaseTimestamp: '2017-02-08T20:01:41+00:00' as Timestamp, }, ] satisfies BranchUpgradeConfig[]; const res = generateBranchConfig(branch); @@ -651,7 +652,7 @@ describe('workers/repository/updates/generate', () => { group: { foo: 2, }, - releaseTimestamp: '2017-02-07T20:01:41+00:00', + releaseTimestamp: '2017-02-07T20:01:41+00:00' as Timestamp, }, { manager: 'some-manager', @@ -667,7 +668,7 @@ describe('workers/repository/updates/generate', () => { group: { foo: 2, }, - releaseTimestamp: '2017-02-08T20:01:41+00:00', + releaseTimestamp: '2017-02-08T20:01:41+00:00' as Timestamp, }, ] satisfies BranchUpgradeConfig[]; const res = generateBranchConfig(branch); diff --git a/lib/workers/repository/updates/generate.ts b/lib/workers/repository/updates/generate.ts index f2637f2fb95a66..c2db51f46b5656 100644 --- a/lib/workers/repository/updates/generate.ts +++ b/lib/workers/repository/updates/generate.ts @@ -9,6 +9,7 @@ import { newlineRegex, regEx } from '../../../util/regex'; import { sanitize } from '../../../util/sanitize'; import { safeStringify } from '../../../util/stringify'; import * as template from '../../../util/template'; +import type { Timestamp } from '../../../util/timestamp'; import { uniq } from '../../../util/uniq'; import type { BranchConfig, BranchUpgradeConfig } from '../../types'; import { CommitMessage } from '../model/commit-message'; @@ -245,7 +246,7 @@ export function generateBranchConfig( logger.trace(`groupEligible: ${groupEligible}`); const useGroupSettings = hasGroupName && groupEligible; logger.trace(`useGroupSettings: ${useGroupSettings}`); - let releaseTimestamp: string; + let releaseTimestamp: Timestamp; if (depTypes.size) { config.depTypes = Array.from(depTypes).sort(); diff --git a/lib/workers/types.ts b/lib/workers/types.ts index e4c6477b45f662..54f0df74db6965 100644 --- a/lib/workers/types.ts +++ b/lib/workers/types.ts @@ -19,6 +19,7 @@ import type { import type { PlatformPrOptions } from '../modules/platform/types'; import type { FileChange } from '../util/git/types'; import type { MergeConfidence } from '../util/merge-confidence/types'; +import type { Timestamp } from '../util/timestamp'; import type { ChangeLogRelease, ChangeLogResult, @@ -41,6 +42,8 @@ export interface BranchUpgradeConfig currentDigest?: string; currentDigestShort?: string; currentValue?: string; + + currentValueTemplate?: string; depIndex?: number; depTypes?: string[]; @@ -65,7 +68,7 @@ export interface BranchUpgradeConfig prettyNewMajor?: string; prettyNewVersion?: string; releases?: ReleaseWithNotes[]; - releaseTimestamp?: string; + releaseTimestamp?: Timestamp; repoName?: string; minimumConfidence?: MergeConfidence | undefined; sourceDirectory?: string; @@ -122,7 +125,7 @@ export interface BranchConfig errors?: ValidationMessage[]; hasTypes?: boolean; dependencyDashboardChecks?: Record; - releaseTimestamp?: string; + releaseTimestamp?: Timestamp; forceCommit?: boolean; rebaseRequested?: boolean; result?: BranchResult; diff --git a/package.json b/package.json index 33f3dd4566e2e8..5fd097b81e6e9b 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "doc-fix": "run-s markdown-lint-fix prettier-fix", "doc-fix-everything": "run-s doc-fix doc-fence-check lint-documentation", "doc-fence-check": "node tools/check-fenced-code.mjs", - "lint-documentation": "jest --coverage false test/documentation.spec.ts", + "lint-documentation": "jest --coverage false test/docs/**.spec.ts", + "lint-other": "jest --coverage false test/other/**.spec.ts", "eslint": "eslint . --cache --cache-location .cache/eslint --report-unused-disable-directives", "eslint-fix": "eslint --cache --cache-location .cache/eslint --fix . --report-unused-disable-directives", "eslint-ci": "eslint . --cache --cache-strategy content --cache-location .cache/eslint --format gha", @@ -53,7 +54,7 @@ "test-e2e:run": "cd test/e2e && npm test", "test-schema": "run-s create-json-schema", "test:docs": "node --test tools/docs/test/**/*.mjs", - "schedule-test-shards": "SCHEDULE_TEST_SHARDS=true ts-node jest.config.ts", + "schedule-test-shards": "SCHEDULE_TEST_SHARDS=true ts-node tools/test-shards.ts", "tsc": "tsc", "type-check": "run-s 'generate:*' 'tsc --noEmit {@}' --", "update-static-data": "run-s 'update-static-data:*'", @@ -139,32 +140,33 @@ "pnpm": "^9.0.0" }, "volta": { - "node": "22.11.0", - "pnpm": "9.15.2" + "node": "22.13.1", + "pnpm": "9.15.4" }, "dependencies": { - "@aws-sdk/client-codecommit": "3.716.0", - "@aws-sdk/client-ec2": "3.716.0", - "@aws-sdk/client-ecr": "3.720.0", - "@aws-sdk/client-rds": "3.719.1", - "@aws-sdk/client-s3": "3.717.0", - "@aws-sdk/credential-providers": "3.716.0", + "@aws-sdk/client-codecommit": "3.738.0", + "@aws-sdk/client-ec2": "3.738.0", + "@aws-sdk/client-ecr": "3.739.0", + "@aws-sdk/client-rds": "3.740.0", + "@aws-sdk/client-s3": "3.740.0", + "@aws-sdk/credential-providers": "3.738.0", "@breejs/later": "4.2.0", - "@cdktf/hcl2json": "0.20.10", + "@cdktf/hcl2json": "0.20.11", "@opentelemetry/api": "1.9.0", - "@opentelemetry/context-async-hooks": "1.30.0", - "@opentelemetry/exporter-trace-otlp-http": "0.57.0", - "@opentelemetry/instrumentation": "0.57.0", + "@opentelemetry/context-async-hooks": "1.30.1", + "@opentelemetry/exporter-trace-otlp-http": "0.57.1", + "@opentelemetry/instrumentation": "0.57.1", "@opentelemetry/instrumentation-bunyan": "0.45.0", - "@opentelemetry/instrumentation-http": "0.57.0", - "@opentelemetry/resources": "1.30.0", - "@opentelemetry/sdk-trace-base": "1.30.0", - "@opentelemetry/sdk-trace-node": "1.30.0", + "@opentelemetry/instrumentation-http": "0.57.1", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/sdk-trace-base": "1.30.1", + "@opentelemetry/sdk-trace-node": "1.30.1", "@opentelemetry/semantic-conventions": "1.28.0", + "@pnpm/parse-overrides": "1000.0.1", "@qnighy/marshal": "0.1.3", "@renovatebot/detect-tools": "1.1.0", "@renovatebot/kbpgp": "4.0.1", - "@renovatebot/osv-offline": "1.5.11", + "@renovatebot/osv-offline": "1.5.12", "@renovatebot/pep440": "4.0.1", "@renovatebot/ruby-semver": "4.0.0", "@sindresorhus/is": "4.6.0", @@ -181,10 +183,10 @@ "chalk": "4.1.2", "changelog-filename-regex": "2.0.1", "clean-git-ref": "2.0.1", - "commander": "12.1.0", + "commander": "13.1.0", "conventional-commits-detector": "1.0.3", "croner": "9.0.0", - "cronstrue": "2.52.0", + "cronstrue": "2.53.0", "deepmerge": "4.3.1", "dequal": "2.0.3", "detect-indent": "6.1.0", @@ -197,17 +199,17 @@ "extract-zip": "2.0.1", "find-packages": "10.0.4", "find-up": "5.0.0", - "fs-extra": "11.2.0", + "fs-extra": "11.3.0", "git-url-parse": "16.0.0", "github-url-from-git": "1.5.0", - "glob": "11.0.0", + "glob": "11.0.1", "global-agent": "3.0.0", "good-enough-parser": "1.1.23", - "google-auth-library": "9.15.0", + "google-auth-library": "9.15.1", "got": "11.8.6", - "graph-data-structure": "4.3.0", + "graph-data-structure": "4.3.1", "handlebars": "4.7.8", - "ignore": "6.0.2", + "ignore": "7.0.3", "ini": "5.0.0", "json-dup-key-validator": "1.0.3", "json-stringify-pretty-compact": "3.0.0", @@ -223,7 +225,7 @@ "ms": "2.1.3", "nanoid": "3.3.8", "neotraverse": "0.6.18", - "node-html-parser": "6.1.13", + "node-html-parser": "7.0.1", "p-all": "3.0.0", "p-map": "4.0.0", "p-queue": "6.6.2", @@ -254,7 +256,7 @@ "zod": "3.24.1" }, "optionalDependencies": { - "better-sqlite3": "11.7.0", + "better-sqlite3": "11.8.1", "openpgp": "6.0.1", "re2": "1.21.4" }, @@ -269,7 +271,7 @@ "@openpgp/web-stream-tools": "0.1.3", "@renovate/eslint-plugin": "file:tools/eslint", "@semantic-release/exec": "6.0.3", - "@swc/core": "1.10.4", + "@swc/core": "1.10.9", "@types/auth-header": "1.0.6", "@types/aws4": "1.11.6", "@types/better-sqlite3": "7.6.12", @@ -281,7 +283,7 @@ "@types/clean-git-ref": "2.0.2", "@types/common-tags": "1.8.4", "@types/conventional-commits-detector": "1.0.2", - "@types/diff": "6.0.0", + "@types/diff": "7.0.0", "@types/eslint": "8.56.12", "@types/fs-extra": "11.0.4", "@types/git-url-parse": "9.0.3", @@ -291,15 +293,15 @@ "@types/js-yaml": "4.0.9", "@types/json-dup-key-validator": "1.0.2", "@types/linkify-markdown": "1.0.3", - "@types/lodash": "4.17.13", + "@types/lodash": "4.17.14", "@types/luxon": "3.4.2", "@types/markdown-it": "14.1.2", "@types/markdown-table": "2.0.0", "@types/marshal": "0.5.3", "@types/mdast": "3.0.15", - "@types/moo": "0.5.9", + "@types/moo": "0.5.10", "@types/ms": "0.7.34", - "@types/node": "20.17.11", + "@types/node": "22.10.9", "@types/parse-link-header": "2.0.3", "@types/punycode": "2.1.4", "@types/semver": "7.5.8", @@ -311,8 +313,8 @@ "@types/url-join": "4.0.3", "@types/validate-npm-package-name": "4.0.2", "@types/xmldoc": "1.1.9", - "@typescript-eslint/eslint-plugin": "8.19.0", - "@typescript-eslint/parser": "8.19.0", + "@typescript-eslint/eslint-plugin": "8.20.0", + "@typescript-eslint/parser": "8.20.0", "aws-sdk-client-mock": "4.1.0", "callsite": "1.0.0", "common-tags": "1.8.2", @@ -322,7 +324,7 @@ "eslint-formatter-gha": "1.5.2", "eslint-import-resolver-typescript": "3.7.0", "eslint-plugin-import": "2.31.0", - "eslint-plugin-jest": "28.10.0", + "eslint-plugin-jest": "28.11.0", "eslint-plugin-jest-formatting": "3.1.0", "eslint-plugin-promise": "7.2.1", "eslint-plugin-typescript-enum": "2.1.0", @@ -335,23 +337,23 @@ "jest-mock": "29.7.0", "jest-mock-extended": "3.0.7", "jest-snapshot": "29.7.0", - "markdownlint-cli2": "0.17.1", - "memfs": "4.15.2", + "markdownlint-cli2": "0.17.2", + "memfs": "4.17.0", "nock": "13.5.6", "npm-run-all2": "7.0.2", "nyc": "17.1.0", "pretty-format": "29.7.0", "rimraf": "6.0.1", - "semantic-release": "24.2.0", + "semantic-release": "24.2.1", "tar": "7.4.3", "tmp-promise": "3.0.3", "ts-jest": "29.2.5", "ts-node": "10.9.2", - "type-fest": "4.31.0", - "typescript": "5.7.2", + "type-fest": "4.33.0", + "typescript": "5.7.3", "unified": "9.2.2" }, - "packageManager": "pnpm@9.15.2", + "packageManager": "pnpm@9.15.4", "files": [ "dist", "renovate-schema.json" @@ -364,7 +366,7 @@ "safe-json-stringify" ], "overrides": { - "@semantic-release/github>@octokit/plugin-paginate-rest": "11.3.6" + "@semantic-release/github>@octokit/plugin-paginate-rest": "11.4.0" } } } diff --git a/pdm.lock b/pdm.lock index 9841fe985b90ba..be933f80efbffc 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:389d88fe2eea354363ad2bc3a292f9398c1ab70d41dec7307928a6d5fe1a11c5" +content_hash = "sha256:cb7ab4ded4a7ff81d1e6f461b57bd06ec2efafa0dbc1f980286e10fbb75c1a57" [[metadata.targets]] requires_python = ">=3.11" @@ -37,13 +37,13 @@ files = [ [[package]] name = "certifi" -version = "2024.12.14" +version = "2025.1.31" requires_python = ">=3.6" summary = "Python package for providing Mozilla's CA Bundle." groups = ["default"] files = [ - {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, - {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, + {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, + {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, ] [[package]] @@ -297,7 +297,7 @@ files = [ [[package]] name = "mkdocs-material" -version = "9.5.49" +version = "9.6.1" requires_python = ">=3.8" summary = "Documentation that simply works" groups = ["default"] @@ -315,8 +315,8 @@ dependencies = [ "requests~=2.26", ] files = [ - {file = "mkdocs_material-9.5.49-py3-none-any.whl", hash = "sha256:c3c2d8176b18198435d3a3e119011922f3e11424074645c24019c2dcf08a360e"}, - {file = "mkdocs_material-9.5.49.tar.gz", hash = "sha256:3671bb282b4f53a1c72e08adbe04d2481a98f85fed392530051f80ff94a9621d"}, + {file = "mkdocs_material-9.6.1-py3-none-any.whl", hash = "sha256:c1742d410be29811a9b7e863cb25a578b9e255fe6f04c69f8c6838863a58e141"}, + {file = "mkdocs_material-9.6.1.tar.gz", hash = "sha256:da37dba220d9fbfc5f1fc567fafc4028e3c3d7d828f2779ed09ab726ceca77dc"}, ] [[package]] @@ -386,18 +386,18 @@ files = [ [[package]] name = "pygments" -version = "2.19.0" +version = "2.19.1" requires_python = ">=3.8" summary = "Pygments is a syntax highlighting package written in Python." groups = ["default"] files = [ - {file = "pygments-2.19.0-py3-none-any.whl", hash = "sha256:4755e6e64d22161d5b61432c0600c923c5927214e7c956e31c23923c89251a9b"}, - {file = "pygments-2.19.0.tar.gz", hash = "sha256:afc4146269910d4bdfabcd27c24923137a74d562a23a320a41a55ad303e19783"}, + {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, + {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, ] [[package]] name = "pymdown-extensions" -version = "10.13" +version = "10.14.2" requires_python = ">=3.8" summary = "Extension pack for Python Markdown." groups = ["default"] @@ -406,8 +406,8 @@ dependencies = [ "pyyaml", ] files = [ - {file = "pymdown_extensions-10.13-py3-none-any.whl", hash = "sha256:80bc33d715eec68e683e04298946d47d78c7739e79d808203df278ee8ef89428"}, - {file = "pymdown_extensions-10.13.tar.gz", hash = "sha256:e0b351494dc0d8d14a1f52b39b1499a00ef1566b4ba23dc74f1eba75c736f5dd"}, + {file = "pymdown_extensions-10.14.2-py3-none-any.whl", hash = "sha256:f45bc5892410e54fd738ab8ccd736098b7ff0cb27fdb4bf24d0a0c6584bc90e1"}, + {file = "pymdown_extensions-10.14.2.tar.gz", hash = "sha256:7a77b8116dc04193f2c01143760a43387bd9dc4aa05efacb7d838885a7791253"}, ] [[package]] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 34e9ccf661095a..87f4559adb1cb6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,66 +5,69 @@ settings: excludeLinksFromLockfile: false overrides: - '@semantic-release/github>@octokit/plugin-paginate-rest': 11.3.6 + '@semantic-release/github>@octokit/plugin-paginate-rest': 11.4.0 importers: .: dependencies: '@aws-sdk/client-codecommit': - specifier: 3.716.0 - version: 3.716.0 + specifier: 3.738.0 + version: 3.738.0 '@aws-sdk/client-ec2': - specifier: 3.716.0 - version: 3.716.0 + specifier: 3.738.0 + version: 3.738.0 '@aws-sdk/client-ecr': - specifier: 3.720.0 - version: 3.720.0 + specifier: 3.739.0 + version: 3.739.0 '@aws-sdk/client-rds': - specifier: 3.719.1 - version: 3.719.1 + specifier: 3.740.0 + version: 3.740.0 '@aws-sdk/client-s3': - specifier: 3.717.0 - version: 3.717.0 + specifier: 3.740.0 + version: 3.740.0 '@aws-sdk/credential-providers': - specifier: 3.716.0 - version: 3.716.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0)) + specifier: 3.738.0 + version: 3.738.0 '@breejs/later': specifier: 4.2.0 version: 4.2.0 '@cdktf/hcl2json': - specifier: 0.20.10 - version: 0.20.10 + specifier: 0.20.11 + version: 0.20.11 '@opentelemetry/api': specifier: 1.9.0 version: 1.9.0 '@opentelemetry/context-async-hooks': - specifier: 1.30.0 - version: 1.30.0(@opentelemetry/api@1.9.0) + specifier: 1.30.1 + version: 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/exporter-trace-otlp-http': - specifier: 0.57.0 - version: 0.57.0(@opentelemetry/api@1.9.0) + specifier: 0.57.1 + version: 0.57.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': - specifier: 0.57.0 - version: 0.57.0(@opentelemetry/api@1.9.0) + specifier: 0.57.1 + version: 0.57.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-bunyan': specifier: 0.45.0 version: 0.45.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-http': - specifier: 0.57.0 - version: 0.57.0(@opentelemetry/api@1.9.0) + specifier: 0.57.1 + version: 0.57.1(@opentelemetry/api@1.9.0) '@opentelemetry/resources': - specifier: 1.30.0 - version: 1.30.0(@opentelemetry/api@1.9.0) + specifier: 1.30.1 + version: 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': - specifier: 1.30.0 - version: 1.30.0(@opentelemetry/api@1.9.0) + specifier: 1.30.1 + version: 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-node': - specifier: 1.30.0 - version: 1.30.0(@opentelemetry/api@1.9.0) + specifier: 1.30.1 + version: 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': specifier: 1.28.0 version: 1.28.0 + '@pnpm/parse-overrides': + specifier: 1000.0.1 + version: 1000.0.1 '@qnighy/marshal': specifier: 0.1.3 version: 0.1.3 @@ -75,8 +78,8 @@ importers: specifier: 4.0.1 version: 4.0.1 '@renovatebot/osv-offline': - specifier: 1.5.11 - version: 1.5.11(encoding@0.1.13) + specifier: 1.5.12 + version: 1.5.12(encoding@0.1.13) '@renovatebot/pep440': specifier: 4.0.1 version: 4.0.1 @@ -126,8 +129,8 @@ importers: specifier: 2.0.1 version: 2.0.1 commander: - specifier: 12.1.0 - version: 12.1.0 + specifier: 13.1.0 + version: 13.1.0 conventional-commits-detector: specifier: 1.0.3 version: 1.0.3 @@ -135,8 +138,8 @@ importers: specifier: 9.0.0 version: 9.0.0 cronstrue: - specifier: 2.52.0 - version: 2.52.0 + specifier: 2.53.0 + version: 2.53.0 deepmerge: specifier: 4.3.1 version: 4.3.1 @@ -174,8 +177,8 @@ importers: specifier: 5.0.0 version: 5.0.0 fs-extra: - specifier: 11.2.0 - version: 11.2.0 + specifier: 11.3.0 + version: 11.3.0 git-url-parse: specifier: 16.0.0 version: 16.0.0 @@ -183,8 +186,8 @@ importers: specifier: 1.5.0 version: 1.5.0 glob: - specifier: 11.0.0 - version: 11.0.0 + specifier: 11.0.1 + version: 11.0.1 global-agent: specifier: 3.0.0 version: 3.0.0 @@ -192,20 +195,20 @@ importers: specifier: 1.1.23 version: 1.1.23 google-auth-library: - specifier: 9.15.0 - version: 9.15.0(encoding@0.1.13) + specifier: 9.15.1 + version: 9.15.1(encoding@0.1.13) got: specifier: 11.8.6 version: 11.8.6 graph-data-structure: - specifier: 4.3.0 - version: 4.3.0 + specifier: 4.3.1 + version: 4.3.1 handlebars: specifier: 4.7.8 version: 4.7.8 ignore: - specifier: 6.0.2 - version: 6.0.2 + specifier: 7.0.3 + version: 7.0.3 ini: specifier: 5.0.0 version: 5.0.0 @@ -252,8 +255,8 @@ importers: specifier: 0.6.18 version: 0.6.18 node-html-parser: - specifier: 6.1.13 - version: 6.1.13 + specifier: 7.0.1 + version: 7.0.1 p-all: specifier: 3.0.0 version: 3.0.0 @@ -340,8 +343,8 @@ importers: version: 3.24.1 optionalDependencies: better-sqlite3: - specifier: 11.7.0 - version: 11.7.0 + specifier: 11.8.1 + version: 11.8.1 openpgp: specifier: 6.0.1 version: 6.0.1 @@ -372,16 +375,16 @@ importers: version: 2.2.3 '@openpgp/web-stream-tools': specifier: 0.1.3 - version: 0.1.3(typescript@5.7.2) + version: 0.1.3(typescript@5.7.3) '@renovate/eslint-plugin': specifier: file:tools/eslint version: '@renovatebot/eslint-plugin@file:tools/eslint' '@semantic-release/exec': specifier: 6.0.3 - version: 6.0.3(semantic-release@24.2.0(typescript@5.7.2)) + version: 6.0.3(semantic-release@24.2.1(typescript@5.7.3)) '@swc/core': - specifier: 1.10.4 - version: 1.10.4 + specifier: 1.10.9 + version: 1.10.9 '@types/auth-header': specifier: 1.0.6 version: 1.0.6 @@ -416,8 +419,8 @@ importers: specifier: 1.0.2 version: 1.0.2 '@types/diff': - specifier: 6.0.0 - version: 6.0.0 + specifier: 7.0.0 + version: 7.0.0 '@types/eslint': specifier: 8.56.12 version: 8.56.12 @@ -446,8 +449,8 @@ importers: specifier: 1.0.3 version: 1.0.3 '@types/lodash': - specifier: 4.17.13 - version: 4.17.13 + specifier: 4.17.14 + version: 4.17.14 '@types/luxon': specifier: 3.4.2 version: 3.4.2 @@ -464,14 +467,14 @@ importers: specifier: 3.0.15 version: 3.0.15 '@types/moo': - specifier: 0.5.9 - version: 0.5.9 + specifier: 0.5.10 + version: 0.5.10 '@types/ms': specifier: 0.7.34 version: 0.7.34 '@types/node': - specifier: 20.17.11 - version: 20.17.11 + specifier: 22.10.9 + version: 22.10.9 '@types/parse-link-header': specifier: 2.0.3 version: 2.0.3 @@ -506,11 +509,11 @@ importers: specifier: 1.1.9 version: 1.1.9 '@typescript-eslint/eslint-plugin': - specifier: 8.19.0 - version: 8.19.0(@typescript-eslint/parser@8.19.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2) + specifier: 8.20.0 + version: 8.20.0(@typescript-eslint/parser@8.20.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3) '@typescript-eslint/parser': - specifier: 8.19.0 - version: 8.19.0(eslint@8.57.1)(typescript@5.7.2) + specifier: 8.20.0 + version: 8.20.0(eslint@8.57.1)(typescript@5.7.3) aws-sdk-client-mock: specifier: 4.1.0 version: 4.1.0 @@ -537,10 +540,10 @@ importers: version: 3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1) eslint-plugin-import: specifier: 2.31.0 - version: 2.31.0(@typescript-eslint/parser@8.19.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1) + version: 2.31.0(@typescript-eslint/parser@8.20.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1) eslint-plugin-jest: - specifier: 28.10.0 - version: 28.10.0(@typescript-eslint/eslint-plugin@8.19.0(@typescript-eslint/parser@8.19.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(jest@29.7.0(@types/node@20.17.11)(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2)))(typescript@5.7.2) + specifier: 28.11.0 + version: 28.11.0(@typescript-eslint/eslint-plugin@8.20.0(@typescript-eslint/parser@8.20.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(jest@29.7.0(@types/node@22.10.9)(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3)))(typescript@5.7.3) eslint-plugin-jest-formatting: specifier: 3.1.0 version: 3.1.0(eslint@8.57.1) @@ -549,7 +552,7 @@ importers: version: 7.2.1(eslint@8.57.1) eslint-plugin-typescript-enum: specifier: 2.1.0 - version: 2.1.0(eslint@8.57.1)(typescript@5.7.2) + version: 2.1.0(eslint@8.57.1)(typescript@5.7.3) expect: specifier: 29.7.0 version: 29.7.0 @@ -564,25 +567,25 @@ importers: version: 9.1.7 jest: specifier: 29.7.0 - version: 29.7.0(@types/node@20.17.11)(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2)) + version: 29.7.0(@types/node@22.10.9)(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3)) jest-extended: specifier: 4.0.2 - version: 4.0.2(jest@29.7.0(@types/node@20.17.11)(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2))) + version: 4.0.2(jest@29.7.0(@types/node@22.10.9)(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3))) jest-mock: specifier: 29.7.0 version: 29.7.0 jest-mock-extended: specifier: 3.0.7 - version: 3.0.7(jest@29.7.0(@types/node@20.17.11)(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2)))(typescript@5.7.2) + version: 3.0.7(jest@29.7.0(@types/node@22.10.9)(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3)))(typescript@5.7.3) jest-snapshot: specifier: 29.7.0 version: 29.7.0 markdownlint-cli2: - specifier: 0.17.1 - version: 0.17.1 + specifier: 0.17.2 + version: 0.17.2 memfs: - specifier: 4.15.2 - version: 4.15.2 + specifier: 4.17.0 + version: 4.17.0 nock: specifier: 13.5.6 version: 13.5.6 @@ -599,8 +602,8 @@ importers: specifier: 6.0.1 version: 6.0.1 semantic-release: - specifier: 24.2.0 - version: 24.2.0(typescript@5.7.2) + specifier: 24.2.1 + version: 24.2.1(typescript@5.7.3) tar: specifier: 7.4.3 version: 7.4.3 @@ -609,16 +612,16 @@ importers: version: 3.0.3 ts-jest: specifier: 29.2.5 - version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.11)(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2)))(typescript@5.7.2) + version: 29.2.5(@babel/core@7.26.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.7))(jest@29.7.0(@types/node@22.10.9)(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3)))(typescript@5.7.3) ts-node: specifier: 10.9.2 - version: 10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2) + version: 10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3) type-fest: - specifier: 4.31.0 - version: 4.31.0 + specifier: 4.33.0 + version: 4.33.0 typescript: - specifier: 5.7.2 - version: 5.7.2 + specifier: 5.7.3 + version: 5.7.3 unified: specifier: 9.2.2 version: 9.2.2 @@ -655,204 +658,192 @@ packages: '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - '@aws-sdk/client-codecommit@3.716.0': - resolution: {integrity: sha512-RHPqbayg/QnpHj5RrAC72RU3ppadnDVG8iq+vV67CL16IA+4j9PtrSbfOuJYML0EpInVZ8kV+JHo70iibJCSDw==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/client-cognito-identity@3.716.0': - resolution: {integrity: sha512-tXMp76f1ZzrZtJwVPnLe28YINbNmwxv595Z6kpi9yc3nB/YUdeBUND8u1dgQd/sVNwZzmgcR6nyXnT+GQkeoUg==} - engines: {node: '>=16.0.0'} + '@aws-sdk/client-codecommit@3.738.0': + resolution: {integrity: sha512-v5Pw7O0lAHJBCEvTwvdAxHhQ/IYY7eujWN0vrSva/4J5fc9wsfP92hBmY7muFAnqLdJ75GUtKXCa7RgJRgY+HA==} + engines: {node: '>=18.0.0'} - '@aws-sdk/client-ec2@3.716.0': - resolution: {integrity: sha512-AKAg47EasPKyFPWcUdf3EsduWIaifuKwyoXZpidhNzdl+LigWwkCfkm8LmZ87+JBuekuMhKc3QIGWLv7wsiP6g==} - engines: {node: '>=16.0.0'} + '@aws-sdk/client-cognito-identity@3.738.0': + resolution: {integrity: sha512-TjPpLZ2qkh+2jQIYtUbNh5D6jv4U0DQIUiLLZOKalUqSK2L9OzTc1463kX076QCpYlAZJNt3FvPyiMab0W8zBg==} + engines: {node: '>=18.0.0'} - '@aws-sdk/client-ecr@3.720.0': - resolution: {integrity: sha512-8/b92aUQsWpqMdd/V/YLjq0oRcPakB27x5U42NzOUtJ3RYJHhylIG81YbAU3ANwcPNytjPjnYDcPG7mMQBASFw==} - engines: {node: '>=16.0.0'} + '@aws-sdk/client-ec2@3.738.0': + resolution: {integrity: sha512-Vl2cCvZ8TQ25lCCxRXaL1YeLRM28ldCuCPEry8kH3e4sXdyOQ7zudftY70YT/3JSuuGgFfG3rVAPHEMwolDUuQ==} + engines: {node: '>=18.0.0'} - '@aws-sdk/client-rds@3.719.1': - resolution: {integrity: sha512-2tz2RkAANBSfQGOjUP/RBGGUBUVx+zxMMw2biaS/Gl7HKW0m4vVH0b9aOmYV463FhlRVtzzWsN13JKX4+f2Wsw==} - engines: {node: '>=16.0.0'} + '@aws-sdk/client-ecr@3.739.0': + resolution: {integrity: sha512-z1efvIHy/EJ8hFQsVpx2mmYhDrDdw995OJ2sRcepoZoqNTKc7+uxAImO34ooYWrlvYpAoWvvFkkXtO2BtqE7zg==} + engines: {node: '>=18.0.0'} - '@aws-sdk/client-s3@3.717.0': - resolution: {integrity: sha512-jzaH8IskAXVnqlZ3/H/ROwrB2HCnq/atlN7Hi7FIfjWvMPf5nfcJKfzJ1MXFX0EQR5qO6X4TbK7rgi7Bjw9NjQ==} - engines: {node: '>=16.0.0'} + '@aws-sdk/client-rds@3.740.0': + resolution: {integrity: sha512-ANAGhMZdnkR1XeBQ2ADa/w4U4DYTcJQHBIJdCVJAb74gcA9eZBxnY1DNzj+6koi0cmS7qHyWvlk5H3lkE4DDig==} + engines: {node: '>=18.0.0'} - '@aws-sdk/client-sso-oidc@3.716.0': - resolution: {integrity: sha512-lA4IB9FzR2KjH7EVCo+mHGFKqdViVyeBQEIX9oVratL/l7P0bMS1fMwgfHOc3ACazqNxBxDES7x08ZCp32y6Lw==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@aws-sdk/client-sts': ^3.716.0 + '@aws-sdk/client-s3@3.740.0': + resolution: {integrity: sha512-X9aQOFJC3TsYwQP3AGcNhfYcFehVEHRKCHtHYOIKv5t1ydSJxpN/v34OrMMKvG1jFWMNkSYiSCVB9ZVo9KUwVA==} + engines: {node: '>=18.0.0'} - '@aws-sdk/client-sso@3.716.0': - resolution: {integrity: sha512-5Nb0jJXce2TclbjG7WVPufwhgV1TRydz1QnsuBtKU0AdViEpr787YrZhPpGnNIM1Dx+R1H/tmAHZnOoohS6D8g==} - engines: {node: '>=16.0.0'} + '@aws-sdk/client-sso@3.734.0': + resolution: {integrity: sha512-oerepp0mut9VlgTwnG5Ds/lb0C0b2/rQ+hL/rF6q+HGKPfGsCuPvFx1GtwGKCXd49ase88/jVgrhcA9OQbz3kg==} + engines: {node: '>=18.0.0'} - '@aws-sdk/client-sts@3.716.0': - resolution: {integrity: sha512-i4SVNsrdXudp8T4bkm7Fi3YWlRnvXCSwvNDqf6nLqSJxqr4CN3VlBELueDyjBK7TAt453/qSif+eNx+bHmwo4Q==} - engines: {node: '>=16.0.0'} + '@aws-sdk/core@3.734.0': + resolution: {integrity: sha512-SxnDqf3vobdm50OLyAKfqZetv6zzwnSqwIwd3jrbopxxHKqNIM/I0xcYjD6Tn+mPig+u7iRKb9q3QnEooFTlmg==} + engines: {node: '>=18.0.0'} - '@aws-sdk/core@3.716.0': - resolution: {integrity: sha512-5DkUiTrbyzO8/W4g7UFEqRFpuhgizayHI/Zbh0wtFMcot8801nJV+MP/YMhdjimlvAr/OqYB08FbGsPyWppMTw==} - engines: {node: '>=16.0.0'} + '@aws-sdk/credential-provider-cognito-identity@3.738.0': + resolution: {integrity: sha512-zh8ATHUjy9CyVrq7qa7ICh4t5OF7mps0A22NY2NyLpSYWOErNnze+FvBi2uh+Jp+VIoojH4R2d9/IHTxENhq7g==} + engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-cognito-identity@3.716.0': - resolution: {integrity: sha512-iHmyB3Z6KjAQcpWW01LRjqbOM2OFVfaiGH6tRylPvJN/GnlITLUnUZi/PBAFk1f+TZ94dQWN961c1L/LFCSg9Q==} - engines: {node: '>=16.0.0'} + '@aws-sdk/credential-provider-env@3.734.0': + resolution: {integrity: sha512-gtRkzYTGafnm1FPpiNO8VBmJrYMoxhDlGPYDVcijzx3DlF8dhWnowuSBCxLSi+MJMx5hvwrX2A+e/q0QAeHqmw==} + engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-env@3.716.0': - resolution: {integrity: sha512-JI2KQUnn2arICwP9F3CnqP1W3nAbm4+meQg/yOhp9X0DMzQiHrHRd4HIrK2vyVgi2/6hGhONY5uLF26yRTA7nQ==} - engines: {node: '>=16.0.0'} + '@aws-sdk/credential-provider-http@3.734.0': + resolution: {integrity: sha512-JFSL6xhONsq+hKM8xroIPhM5/FOhiQ1cov0lZxhzZWj6Ai3UAjucy3zyIFDr9MgP1KfCYNdvyaUq9/o+HWvEDg==} + engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-http@3.716.0': - resolution: {integrity: sha512-CZ04pl2z7igQPysQyH2xKZHM3fLwkemxQbKOlje3TmiS1NwXvcKvERhp9PE/H23kOL7beTM19NMRog/Fka/rlw==} - engines: {node: '>=16.0.0'} + '@aws-sdk/credential-provider-ini@3.734.0': + resolution: {integrity: sha512-HEyaM/hWI7dNmb4NhdlcDLcgJvrilk8G4DQX6qz0i4pBZGC2l4iffuqP8K6ZQjUfz5/6894PzeFuhTORAMd+cg==} + engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-ini@3.716.0': - resolution: {integrity: sha512-P37We2GtZvdROxiwP0zrpEL81/HuYK1qlYxp5VCj3uV+G4mG8UQN2gMIU/baYrpOQqa0h81RfyQGRFUjVaDVqw==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@aws-sdk/client-sts': ^3.716.0 + '@aws-sdk/credential-provider-node@3.738.0': + resolution: {integrity: sha512-3MuREsazwBxghKb2sQQHvie+uuK4dX4/ckFYiSoffzJQd0YHxaGxf8cr4NOSCQCUesWu8D3Y0SzlnHGboVSkpA==} + engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-node@3.716.0': - resolution: {integrity: sha512-FGQPK2uKfS53dVvoskN/s/t6m0Po24BGd1PzJdzHBFCOjxbZLM6+8mDMXeyi2hCLVVQOUcuW41kOgmJ0+zMbww==} - engines: {node: '>=16.0.0'} + '@aws-sdk/credential-provider-process@3.734.0': + resolution: {integrity: sha512-zvjsUo+bkYn2vjT+EtLWu3eD6me+uun+Hws1IyWej/fKFAqiBPwyeyCgU7qjkiPQSXqk1U9+/HG9IQ6Iiz+eBw==} + engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-process@3.716.0': - resolution: {integrity: sha512-0spcu2MWVVHSTHH3WE2E//ttUJPwXRM3BCp+WyI41xLzpNu1Fd8zjOrDpEo0SnGUzsSiRTIJWgkuu/tqv9NJ2A==} - engines: {node: '>=16.0.0'} + '@aws-sdk/credential-provider-sso@3.734.0': + resolution: {integrity: sha512-cCwwcgUBJOsV/ddyh1OGb4gKYWEaTeTsqaAK19hiNINfYV/DO9r4RMlnWAo84sSBfJuj9shUNsxzyoe6K7R92Q==} + engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-sso@3.716.0': - resolution: {integrity: sha512-J2IA3WuCpRGGoZm6VHZVFCnrxXP+41iUWb9Ct/1spljegTa1XjiaZ5Jf3+Ubj7WKiyvP9/dgz1L0bu2bYEjliw==} - engines: {node: '>=16.0.0'} + '@aws-sdk/credential-provider-web-identity@3.734.0': + resolution: {integrity: sha512-t4OSOerc+ppK541/Iyn1AS40+2vT/qE+MFMotFkhCgCJbApeRF2ozEdnDN6tGmnl4ybcUuxnp9JWLjwDVlR/4g==} + engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-web-identity@3.716.0': - resolution: {integrity: sha512-vzgpWKs2gGXZGdbMKRFrMW4PqEFWkGvwWH2T7ZwQv9m+8lQ7P4Dk2uimqu0f37HZAbpn8HFMqRh4CaySjU354A==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@aws-sdk/client-sts': ^3.716.0 + '@aws-sdk/credential-providers@3.738.0': + resolution: {integrity: sha512-Ff+7NMLmK9oadO1uHiMCS/V3Pmp3WnY7Ijy4ySx2HLUZQq7EKFZyFB0qslkeawdY0PGWqyj25anh8I/bhxqWoQ==} + engines: {node: '>=18.0.0'} - '@aws-sdk/credential-providers@3.716.0': - resolution: {integrity: sha512-UsalnK1MMfbI8Chb7BFghUvXf+zdqqiZLpSJp9ytXe0/thoafsi2jo0pyFeU08uarU/YA3Usl15I4SdK5uQr1A==} - engines: {node: '>=16.0.0'} + '@aws-sdk/middleware-bucket-endpoint@3.734.0': + resolution: {integrity: sha512-etC7G18aF7KdZguW27GE/wpbrNmYLVT755EsFc8kXpZj8D6AFKxc7OuveinJmiy0bYXAMspJUWsF6CrGpOw6CQ==} + engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-bucket-endpoint@3.714.0': - resolution: {integrity: sha512-I/xSOskiseJJ8i183Z522BgqbgYzLKP7jGcg2Qeib/IWoG2IP+9DH8pwqagKaPAycyswtnoKBJiiFXY43n0CkA==} - engines: {node: '>=16.0.0'} + '@aws-sdk/middleware-expect-continue@3.734.0': + resolution: {integrity: sha512-P38/v1l6HjuB2aFUewt7ueAW5IvKkFcv5dalPtbMGRhLeyivBOHwbCyuRKgVs7z7ClTpu9EaViEGki2jEQqEsQ==} + engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-expect-continue@3.714.0': - resolution: {integrity: sha512-rlzsXdG8Lzo4Qpl35ZnpOBAWlzvDHpP9++0AXoUwAJA0QmMm7auIRmgxJuNj91VwT9h15ZU6xjU4S7fJl4W0+w==} - engines: {node: '>=16.0.0'} + '@aws-sdk/middleware-flexible-checksums@3.735.0': + resolution: {integrity: sha512-Tx7lYTPwQFRe/wQEHMR6Drh/S+X0ToAEq1Ava9QyxV1riwtepzRLojpNDELFb3YQVVYbX7FEiBMCJLMkmIIY+A==} + engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-flexible-checksums@3.717.0': - resolution: {integrity: sha512-a5kY5r7/7bDZZlOQQGWOR1ulQewdtNexdW1Ex5DD0FLKlFY7RD0va24hxQ6BP7mWHol+Dx4pj6UQ8ahk0ap1tw==} - engines: {node: '>=16.0.0'} + '@aws-sdk/middleware-host-header@3.734.0': + resolution: {integrity: sha512-LW7RRgSOHHBzWZnigNsDIzu3AiwtjeI2X66v+Wn1P1u+eXssy1+up4ZY/h+t2sU4LU36UvEf+jrZti9c6vRnFw==} + engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-host-header@3.714.0': - resolution: {integrity: sha512-6l68kjNrh5QC8FGX3I3geBDavWN5Tg1RLHJ2HLA8ByGBtJyCwnz3hEkKfaxn0bBx0hF9DzbfjEOUF6cDqy2Kjg==} - engines: {node: '>=16.0.0'} + '@aws-sdk/middleware-location-constraint@3.734.0': + resolution: {integrity: sha512-EJEIXwCQhto/cBfHdm3ZOeLxd2NlJD+X2F+ZTOxzokuhBtY0IONfC/91hOo5tWQweerojwshSMHRCKzRv1tlwg==} + engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-location-constraint@3.714.0': - resolution: {integrity: sha512-MX7M+V+FblujKck3fyuzePVIAy9530gY719IiSxV6uN1qLHl7VDJxNblpF/KpXakD6rOg8OpvtmqsXj9aBMftw==} - engines: {node: '>=16.0.0'} + '@aws-sdk/middleware-logger@3.734.0': + resolution: {integrity: sha512-mUMFITpJUW3LcKvFok176eI5zXAUomVtahb9IQBwLzkqFYOrMJvWAvoV4yuxrJ8TlQBG8gyEnkb9SnhZvjg67w==} + engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-logger@3.714.0': - resolution: {integrity: sha512-RkqHlMvQWUaRklU1bMfUuBvdWwxgUtEqpADaHXlGVj3vtEY2UgBjy+57CveC4MByqKIunNvVHBBbjrGVtwY7Lg==} - engines: {node: '>=16.0.0'} + '@aws-sdk/middleware-recursion-detection@3.734.0': + resolution: {integrity: sha512-CUat2d9ITsFc2XsmeiRQO96iWpxSKYFjxvj27Hc7vo87YUHRnfMfnc8jw1EpxEwMcvBD7LsRa6vDNky6AjcrFA==} + engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-recursion-detection@3.714.0': - resolution: {integrity: sha512-AVU5ixnh93nqtsfgNc284oXsXaadyHGPHpql/jwgaaqQfEXjS/1/j3j9E/vpacfTTz2Vzo7hAOjnvrOXSEVDaA==} - engines: {node: '>=16.0.0'} + '@aws-sdk/middleware-sdk-ec2@3.734.0': + resolution: {integrity: sha512-EqK7je08OlGCdoPFX5FWL7Th55XYlQS1w7ACGpCxZhxA2hGJLkMmqkw67e4KAvLprL02sOjhnhPjDh5QCEfI1Q==} + engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-sdk-ec2@3.716.0': - resolution: {integrity: sha512-7d2uBQwl9maxINe0cWyi6LY76jqUi0saEvwPH6RbKTqW3OeYKsPNzx/48oWK6byBnLZAxeplN38aWPCcCW207g==} - engines: {node: '>=16.0.0'} + '@aws-sdk/middleware-sdk-rds@3.734.0': + resolution: {integrity: sha512-B7aFCj1XFpGkjN6TlyAxVIZxN2u+wmzeNmBUGGpRLSjZgig8V2z9hnrKFsKxVV2Cey0WnnOM6lmnlDaQtZpwQA==} + engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-sdk-rds@3.716.0': - resolution: {integrity: sha512-88Dg1NRWAYHa6kdl9w4Aw8OIhdW9K9xFBjG3A6SjPgpNGVdCWfwXhDmnh1cyG0IsjXx6u8nfeL8e0ZGmjss50Q==} - engines: {node: '>=16.0.0'} + '@aws-sdk/middleware-sdk-s3@3.740.0': + resolution: {integrity: sha512-VML9TzNoQdAs5lSPQSEgZiPgMUSz2H7SltaLb9g4tHwKK5xQoTq5WcDd6V1d2aPxSN5Q2Q63aiVUBby6MdUN/Q==} + engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-sdk-s3@3.716.0': - resolution: {integrity: sha512-Qzz5OfRA/5brqfvq+JHTInwS1EuJ1+tC6qMtwKWJN3czMnVJVdnnsPTf+G5IM/1yYaGEIjY8rC1ExQLcc8ApFQ==} - engines: {node: '>=16.0.0'} + '@aws-sdk/middleware-ssec@3.734.0': + resolution: {integrity: sha512-d4yd1RrPW/sspEXizq2NSOUivnheac6LPeLSLnaeTbBG9g1KqIqvCzP1TfXEqv2CrWfHEsWtJpX7oyjySSPvDQ==} + engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-ssec@3.714.0': - resolution: {integrity: sha512-RkK8REAVwNUQmYbIDRw8eYbMJ8F1Rw4C9mlME4BBMhFlelGcD3ErU2ce24moQbDxBjNwHNESmIqgmdQk93CDCQ==} - engines: {node: '>=16.0.0'} + '@aws-sdk/middleware-user-agent@3.734.0': + resolution: {integrity: sha512-MFVzLWRkfFz02GqGPjqSOteLe5kPfElUrXZft1eElnqulqs6RJfVSpOV7mO90gu293tNAeggMWAVSGRPKIYVMg==} + engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-user-agent@3.716.0': - resolution: {integrity: sha512-FpAtT6nNKrYdkDZndutEraiRMf+TgDzAGvniqRtZ/YTPA+gIsWrsn+TwMKINR81lFC3nQfb9deS5CFtxd021Ew==} - engines: {node: '>=16.0.0'} + '@aws-sdk/nested-clients@3.734.0': + resolution: {integrity: sha512-iph2XUy8UzIfdJFWo1r0Zng9uWj3253yvW9gljhtu+y/LNmNvSnJxQk1f3D2BC5WmcoPZqTS3UsycT3mLPSzWA==} + engines: {node: '>=18.0.0'} - '@aws-sdk/region-config-resolver@3.714.0': - resolution: {integrity: sha512-HJzsQxgMOAzZrbf/YIqEx30or4tZK1oNAk6Wm6xecUQx+23JXIaePRu1YFUOLBBERQ4QBPpISFurZWBMZ5ibAw==} - engines: {node: '>=16.0.0'} + '@aws-sdk/region-config-resolver@3.734.0': + resolution: {integrity: sha512-Lvj1kPRC5IuJBr9DyJ9T9/plkh+EfKLy+12s/mykOy1JaKHDpvj+XGy2YO6YgYVOb8JFtaqloid+5COtje4JTQ==} + engines: {node: '>=18.0.0'} - '@aws-sdk/signature-v4-multi-region@3.716.0': - resolution: {integrity: sha512-k0goWotZKKz+kV6Ln0qeAMSeSVi4NipuIIz5R8A0uCF2zBK4CXWdZR7KeaIoLBhJwQnHj1UU7E+2MK74KIUBzA==} - engines: {node: '>=16.0.0'} + '@aws-sdk/signature-v4-multi-region@3.740.0': + resolution: {integrity: sha512-w+psidN3i+kl51nQEV3V+fKjKUqcEbqUA1GtubruDBvBqrl5El/fU2NF3Lo53y8CfI9wCdf3V7KOEpHIqxHNng==} + engines: {node: '>=18.0.0'} - '@aws-sdk/token-providers@3.714.0': - resolution: {integrity: sha512-vKN064aLE3kl+Zl16Ony3jltHnMddMBT7JRkP1L+lLywhA0PcAKxpdvComul/sTBWnbnwLnaS5NsDUhcWySH8A==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@aws-sdk/client-sso-oidc': ^3.714.0 + '@aws-sdk/token-providers@3.734.0': + resolution: {integrity: sha512-2U6yWKrjWjZO8Y5SHQxkFvMVWHQWbS0ufqfAIBROqmIZNubOL7jXCiVdEFekz6MZ9LF2tvYGnOW4jX8OKDGfIw==} + engines: {node: '>=18.0.0'} - '@aws-sdk/types@3.714.0': - resolution: {integrity: sha512-ZjpP2gYbSFlxxaUDa1Il5AVvfggvUPbjzzB/l3q0gIE5Thd6xKW+yzEpt2mLZ5s5UaYSABZbF94g8NUOF4CVGA==} - engines: {node: '>=16.0.0'} + '@aws-sdk/types@3.734.0': + resolution: {integrity: sha512-o11tSPTT70nAkGV1fN9wm/hAIiLPyWX6SuGf+9JyTp7S/rC2cFWhR26MvA69nplcjNaXVzB0f+QFrLXXjOqCrg==} + engines: {node: '>=18.0.0'} - '@aws-sdk/util-arn-parser@3.693.0': - resolution: {integrity: sha512-WC8x6ca+NRrtpAH64rWu+ryDZI3HuLwlEr8EU6/dbC/pt+r/zC0PBoC15VEygUaBA+isppCikQpGyEDu0Yj7gQ==} - engines: {node: '>=16.0.0'} + '@aws-sdk/util-arn-parser@3.723.0': + resolution: {integrity: sha512-ZhEfvUwNliOQROcAk34WJWVYTlTa4694kSVhDSjW6lE1bMataPnIN8A0ycukEzBXmd8ZSoBcQLn6lKGl7XIJ5w==} + engines: {node: '>=18.0.0'} - '@aws-sdk/util-endpoints@3.714.0': - resolution: {integrity: sha512-Xv+Z2lhe7w7ZZRsgBwBMZgGTVmS+dkkj2S13uNHAx9lhB5ovM8PhK5G/j28xYf6vIibeuHkRAbb7/ozdZIGR+A==} - engines: {node: '>=16.0.0'} + '@aws-sdk/util-endpoints@3.734.0': + resolution: {integrity: sha512-w2+/E88NUbqql6uCVAsmMxDQKu7vsKV0KqhlQb0lL+RCq4zy07yXYptVNs13qrnuTfyX7uPXkXrlugvK9R1Ucg==} + engines: {node: '>=18.0.0'} - '@aws-sdk/util-format-url@3.714.0': - resolution: {integrity: sha512-PA/ES6BeKmYzFOsZ3az/8MqSLf6uzXAS7GsYONZMF6YASn4ewd/AspuvQMp6+x9VreAPCq7PecF+XL9KXejtPg==} - engines: {node: '>=16.0.0'} + '@aws-sdk/util-format-url@3.734.0': + resolution: {integrity: sha512-TxZMVm8V4aR/QkW9/NhujvYpPZjUYqzLwSge5imKZbWFR806NP7RMwc5ilVuHF/bMOln/cVHkl42kATElWBvNw==} + engines: {node: '>=18.0.0'} - '@aws-sdk/util-locate-window@3.693.0': - resolution: {integrity: sha512-ttrag6haJLWABhLqtg1Uf+4LgHWIMOVSYL+VYZmAp2v4PUGOwWmWQH0Zk8RM7YuQcLfH/EoR72/Yxz6A4FKcuw==} - engines: {node: '>=16.0.0'} + '@aws-sdk/util-locate-window@3.723.0': + resolution: {integrity: sha512-Yf2CS10BqK688DRsrKI/EO6B8ff5J86NXe4C+VCysK7UOgN0l1zOTeTukZ3H8Q9tYYX3oaF1961o8vRkFm7Nmw==} + engines: {node: '>=18.0.0'} - '@aws-sdk/util-user-agent-browser@3.714.0': - resolution: {integrity: sha512-OdJJ03cP9/MgIVToPJPCPUImbpZzTcwdIgbXC0tUQPJhbD7b7cB4LdnkhNHko+MptpOrCq4CPY/33EpOjRdofw==} + '@aws-sdk/util-user-agent-browser@3.734.0': + resolution: {integrity: sha512-xQTCus6Q9LwUuALW+S76OL0jcWtMOVu14q+GoLnWPUM7QeUw963oQcLhF7oq0CtaLLKyl4GOUfcwc773Zmwwng==} - '@aws-sdk/util-user-agent-node@3.716.0': - resolution: {integrity: sha512-3PqaXmQbxrtHKAsPCdp7kn5FrQktj8j3YyuNsqFZ8rWZeEQ88GWlsvE61PTsr2peYCKzpFqYVddef2x1axHU0w==} - engines: {node: '>=16.0.0'} + '@aws-sdk/util-user-agent-node@3.734.0': + resolution: {integrity: sha512-c6Iinh+RVQKs6jYUFQ64htOU2HUXFQ3TVx+8Tu3EDF19+9vzWi9UukhIMH9rqyyEXIAkk9XL7avt8y2Uyw2dGA==} + engines: {node: '>=18.0.0'} peerDependencies: aws-crt: '>=1.0.0' peerDependenciesMeta: aws-crt: optional: true - '@aws-sdk/xml-builder@3.709.0': - resolution: {integrity: sha512-2GPCwlNxeHspoK/Mc8nbk9cBOkSpp3j2SJUQmFnyQK6V/pR6II2oPRyZkMomug1Rc10hqlBHByMecq4zhV2uUw==} - engines: {node: '>=16.0.0'} + '@aws-sdk/xml-builder@3.734.0': + resolution: {integrity: sha512-Zrjxi5qwGEcUsJ0ru7fRtW74WcTS0rbLcehoFB+rN1GRi2hbLcFaYs4PwVA5diLeAJH0gszv3x4Hr/S87MfbKQ==} + engines: {node: '>=18.0.0'} '@babel/code-frame@7.26.2': resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.26.3': - resolution: {integrity: sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==} + '@babel/compat-data@7.26.5': + resolution: {integrity: sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==} engines: {node: '>=6.9.0'} - '@babel/core@7.26.0': - resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} + '@babel/core@7.26.7': + resolution: {integrity: sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.26.3': - resolution: {integrity: sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==} + '@babel/generator@7.26.5': + resolution: {integrity: sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.25.9': - resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} + '@babel/helper-compilation-targets@7.26.5': + resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==} engines: {node: '>=6.9.0'} '@babel/helper-module-imports@7.25.9': @@ -865,8 +856,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-plugin-utils@7.25.9': - resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} + '@babel/helper-plugin-utils@7.26.5': + resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} engines: {node: '>=6.9.0'} '@babel/helper-string-parser@7.25.9': @@ -881,12 +872,12 @@ packages: resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.26.0': - resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} + '@babel/helpers@7.26.7': + resolution: {integrity: sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==} engines: {node: '>=6.9.0'} - '@babel/parser@7.26.3': - resolution: {integrity: sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==} + '@babel/parser@7.26.7': + resolution: {integrity: sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==} engines: {node: '>=6.0.0'} hasBin: true @@ -981,20 +972,20 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime-corejs3@7.26.0': - resolution: {integrity: sha512-YXHu5lN8kJCb1LOb9PgV6pvak43X2h4HvRApcN5SdWeaItQOzfn1hgP6jasD6KWQyJDBxrVmA9o9OivlnNJK/w==} + '@babel/runtime-corejs3@7.26.7': + resolution: {integrity: sha512-55gRV8vGrCIYZnaQHQrD92Lo/hYE3Sj5tmbuf0hhHR7sj2CWhEhHU89hbq+UVDXvFG1zUVXJhUkEq1eAfqXtFw==} engines: {node: '>=6.9.0'} '@babel/template@7.25.9': resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.26.4': - resolution: {integrity: sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==} + '@babel/traverse@7.26.7': + resolution: {integrity: sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==} engines: {node: '>=6.9.0'} - '@babel/types@7.26.3': - resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==} + '@babel/types@7.26.7': + resolution: {integrity: sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==} engines: {node: '>=6.9.0'} '@bcoe/v8-coverage@0.2.3': @@ -1004,8 +995,8 @@ packages: resolution: {integrity: sha512-EVMD0SgJtOuFeg0lAVbCwa+qeTKILb87jqvLyUtQswGD9+ce2nB52Y5zbTF1Hc0MDFfbydcMcxb47jSdhikVHA==} engines: {node: '>= 10'} - '@cdktf/hcl2json@0.20.10': - resolution: {integrity: sha512-oh8g9727sSEnwRjIjPYnhTn4mvvxclRcovf5GIkFED+4HpiBLioVUUsX33rFk6wZZh175uJBOAvQ/qVG+Uaqyw==} + '@cdktf/hcl2json@0.20.11': + resolution: {integrity: sha512-k4CJkbUPyI+k9KOQjJ6qu2dIrpqSkXukt9R+kDaizWVM4yc8HDMLHnelC0X2oWsfeQNE8wSAm20SXkGlPLoFmw==} '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} @@ -1251,8 +1242,8 @@ packages: resolution: {integrity: sha512-bdlj/CJVjpaz06NBpfHhp4kGJaRZfz7AzC+6EwUImRtrwIw8dIgJ63Xg0OzV9pRn3rIzrt5c2sa++BL0JJ8GLw==} engines: {node: '>= 18'} - '@octokit/openapi-types@22.2.0': - resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==} + '@octokit/openapi-types@23.0.1': + resolution: {integrity: sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==} '@octokit/plugin-paginate-rest@11.3.1': resolution: {integrity: sha512-ryqobs26cLtM1kQxqeZui4v8FeznirUsksiA+RYemMPJ7Micju0WSkv50dBksTuZks9O5cg4wp+t8fZ/cLY56g==} @@ -1260,8 +1251,8 @@ packages: peerDependencies: '@octokit/core': '5' - '@octokit/plugin-paginate-rest@11.3.6': - resolution: {integrity: sha512-zcvqqf/+TicbTCa/Z+3w4eBJcAxCFymtc0UAIsR3dEVoNilWld4oXdscQ3laXamTszUZdusw97K8+DrbFiOwjw==} + '@octokit/plugin-paginate-rest@11.4.0': + resolution: {integrity: sha512-ttpGck5AYWkwMkMazNCZMqxKqIq1fJBNxBfsFwwfyYKTf914jKkLF0POMS3YkPBwp5g1c2Y4L79gDz01GhSr1g==} engines: {node: '>= 18'} peerDependencies: '@octokit/core': '>=6' @@ -1278,17 +1269,17 @@ packages: peerDependencies: '@octokit/core': ^5 - '@octokit/plugin-retry@7.1.2': - resolution: {integrity: sha512-XOWnPpH2kJ5VTwozsxGurw+svB2e61aWlmk5EVIYZPwFK5F9h4cyPyj9CIKRyMXMHSwpIsI3mPOdpMmrRhe7UQ==} + '@octokit/plugin-retry@7.1.3': + resolution: {integrity: sha512-8nKOXvYWnzv89gSyIvgFHmCBAxfQAOPRlkacUHL9r5oWtp5Whxl8Skb2n3ACZd+X6cYijD6uvmrQuPH/UCL5zQ==} engines: {node: '>= 18'} peerDependencies: '@octokit/core': '>=6' - '@octokit/plugin-throttling@9.3.2': - resolution: {integrity: sha512-FqpvcTpIWFpMMwIeSoypoJXysSAQ3R+ALJhXXSG1HTP3YZOIeLmcNcimKaXxTcws+Sh6yoRl13SJ5r8sXc1Fhw==} + '@octokit/plugin-throttling@9.4.0': + resolution: {integrity: sha512-IOlXxXhZA4Z3m0EEYtrrACkuHiArHLZ3CvqWwOez/pURNqRuwfoFlTPbN5Muf28pzFuztxPyiUiNwz8KctdZaQ==} engines: {node: '>= 18'} peerDependencies: - '@octokit/core': ^6.0.0 + '@octokit/core': ^6.1.3 '@octokit/request-error@5.1.0': resolution: {integrity: sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==} @@ -1302,16 +1293,16 @@ packages: resolution: {integrity: sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==} engines: {node: '>= 18'} - '@octokit/request@9.1.4': - resolution: {integrity: sha512-tMbOwGm6wDII6vygP3wUVqFTw3Aoo0FnVQyhihh8vVq12uO3P+vQZeo2CKMpWtPSogpACD0yyZAlVlQnjW71DA==} + '@octokit/request@9.2.0': + resolution: {integrity: sha512-kXLfcxhC4ozCnAXy2ff+cSxpcF0A1UqxjvYMqNuPIeOAzJbVWQ+dy5G2fTylofB/gTbObT8O6JORab+5XtA1Kw==} engines: {node: '>= 18'} '@octokit/rest@20.1.1': resolution: {integrity: sha512-MB4AYDsM5jhIHro/dq4ix1iWTLGToIGk6cWF5L6vanFaMble5jTX/UBQyiv05HsWnwUtY8JrfHy2LWfKwihqMw==} engines: {node: '>= 18'} - '@octokit/types@13.6.2': - resolution: {integrity: sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==} + '@octokit/types@13.7.0': + resolution: {integrity: sha512-BXfRP+3P3IN6fd4uF3SniaHKOO4UXWBfkdR3vA8mIvaoO/wLjGN5qivUtW0QRitBHHMcfC41SLhNVYIZZE+wkA==} '@one-ini/wasm@0.1.1': resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} @@ -1325,28 +1316,28 @@ packages: typescript: optional: true - '@opentelemetry/api-logs@0.57.0': - resolution: {integrity: sha512-l1aJ30CXeauVYaI+btiynHpw341LthkMTv3omi1VJDX14werY2Wmv9n1yudMsq9HuY0m8PvXEVX4d8zxEb+WRg==} + '@opentelemetry/api-logs@0.57.1': + resolution: {integrity: sha512-I4PHczeujhQAQv6ZBzqHYEUiggZL4IdSMixtVD3EYqbdrjujE7kRfI5QohjlPoJm8BvenoW5YaTMWRrbpot6tg==} engines: {node: '>=14'} '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} - '@opentelemetry/context-async-hooks@1.30.0': - resolution: {integrity: sha512-roCetrG/cz0r/gugQm/jFo75UxblVvHaNSRoR0kSSRSzXFAiIBqFCZuH458BHBNRtRe+0yJdIJ21L9t94bw7+g==} + '@opentelemetry/context-async-hooks@1.30.1': + resolution: {integrity: sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/core@1.30.0': - resolution: {integrity: sha512-Q/3u/K73KUjTCnFUP97ZY+pBjQ1kPEgjOfXj/bJl8zW7GbXdkw6cwuyZk6ZTXkVgCBsYRYUzx4fvYK1jxdb9MA==} + '@opentelemetry/core@1.30.1': + resolution: {integrity: sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/exporter-trace-otlp-http@0.57.0': - resolution: {integrity: sha512-BJl35PSkwoMlGEOrzjCG1ih6zqZoAZJIR4xyqSKC2BqPtwuRjID0vWBaEdP9xrxxJTEIEQw+gEY/0pUgicX0ew==} + '@opentelemetry/exporter-trace-otlp-http@0.57.1': + resolution: {integrity: sha512-43dLEjlf6JGxpVt9RaRlJAvjHG1wGsbAuNd67RIDy/95zfKk2aNovtiGUgFdS/kcvgvS90upIUbgn0xUd9JjMg==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 @@ -1357,68 +1348,68 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-http@0.57.0': - resolution: {integrity: sha512-GJD6e/YSSZUI/xZokK9L+ghMAyFrtGV+8HHXCnV8tDYCo66biLpmC9BUTg6fBnv26QsosYvFTYbdo6Sfn6TxCw==} + '@opentelemetry/instrumentation-http@0.57.1': + resolution: {integrity: sha512-ThLmzAQDs7b/tdKI3BV2+yawuF09jF111OFsovqT1Qj3D8vjwKBwhi/rDE5xethwn4tSXtZcJ9hBsVAlWFQZ7g==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation@0.57.0': - resolution: {integrity: sha512-qIKp+tSCLqofneUWRc5XHtr9jHIq0N0BJfaJamM9gjEFO8sthV4SDXDGNOSAx16PxkbrQJ5/AxMPAGCXl8W/Hg==} + '@opentelemetry/instrumentation@0.57.1': + resolution: {integrity: sha512-SgHEKXoVxOjc20ZYusPG3Fh+RLIZTSa4x8QtD3NfgAUDyqdFFS9W1F2ZVbZkqDCdyMcQG02Ok4duUGLHJXHgbA==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-exporter-base@0.57.0': - resolution: {integrity: sha512-QQl4Ngm3D6H8SDO0EM642ncTxjRsf/HDq7+IWIA0eaEK/NTsJeQ3iYJiZj3F4jkALnvyeM1kkwd+DHtqxTBx9Q==} + '@opentelemetry/otlp-exporter-base@0.57.1': + resolution: {integrity: sha512-GNBJAEYfeiYJQ3O2dvXgiNZ/qjWrBxSb1L1s7iV/jKBRGMN3Nv+miTk2SLeEobF5E5ZK4rVcHKlBZ71bPVIv/g==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-transformer@0.57.0': - resolution: {integrity: sha512-yHX7sdwkdAmSa6Jbi3caSLDWy0PCHS1pKQeKz8AIWSyQqL7IojHKgdk9A+7eRd98Z1n9YTdwWSWLnObvIqhEhQ==} + '@opentelemetry/otlp-transformer@0.57.1': + resolution: {integrity: sha512-EX67y+ukNNfFrOLyjYGw8AMy0JPIlEX1dW60SGUNZWW2hSQyyolX7EqFuHP5LtXLjJHNfzx5SMBVQ3owaQCNDw==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/propagator-b3@1.30.0': - resolution: {integrity: sha512-lcobQQmd+hLdtxJJKu/i51lNXmF1PJJ7Y9B97ciHRVQuMI260vSZG7Uf4Zg0fqR8PB+fT/7rnlDwS0M7QldZQQ==} + '@opentelemetry/propagator-b3@1.30.1': + resolution: {integrity: sha512-oATwWWDIJzybAZ4pO76ATN5N6FFbOA1otibAVlS8v90B4S1wClnhRUk7K+2CHAwN1JKYuj4jh/lpCEG5BAqFuQ==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/propagator-jaeger@1.30.0': - resolution: {integrity: sha512-0hdP495V6HPRkVpowt54+Swn5NdesMIRof+rlp0mbnuIUOM986uF+eNxnPo9q5MmJegVBRTxgMHXXwvnXRnKRg==} + '@opentelemetry/propagator-jaeger@1.30.1': + resolution: {integrity: sha512-Pj/BfnYEKIOImirH76M4hDaBSx6HyZ2CXUqk+Kj02m6BB80c/yo4BdWkn/1gDFfU+YPY+bPR2U0DKBfdxCKwmg==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/resources@1.30.0': - resolution: {integrity: sha512-5mGMjL0Uld/99t7/pcd7CuVtJbkARckLVuiOX84nO8RtLtIz0/J6EOHM2TGvPZ6F4K+XjUq13gMx14w80SVCQg==} + '@opentelemetry/resources@1.30.1': + resolution: {integrity: sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/sdk-logs@0.57.0': - resolution: {integrity: sha512-6Kbxdu/QE9LWH7+WSLmYo3DjAq+c55TiCLXiXu6b/2m2muy5SyOG2m0MrGqetyRpfYSSbIqHmJoqNVTN3+2a9g==} + '@opentelemetry/sdk-logs@0.57.1': + resolution: {integrity: sha512-jGdObb/BGWu6Peo3cL3skx/Rl1Ak/wDDO3vpPrrThGbqE7isvkCsX6uE+OAt8Ayjm9YC8UGkohWbLR09JmM0FA==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': '>=1.4.0 <1.10.0' - '@opentelemetry/sdk-metrics@1.30.0': - resolution: {integrity: sha512-5kcj6APyRMvv6dEIP5plz2qfJAD4OMipBRT11u/pa1a68rHKI2Ln+iXVkAGKgx8o7CXbD7FdPypTUY88ZQgP4Q==} + '@opentelemetry/sdk-metrics@1.30.1': + resolution: {integrity: sha512-q9zcZ0Okl8jRgmy7eNW3Ku1XSgg3sDLa5evHZpCwjspw7E8Is4K/haRPDJrBcX3YSn/Y7gUvFnByNYEKQNbNog==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-trace-base@1.30.0': - resolution: {integrity: sha512-RKQDaDIkV7PwizmHw+rE/FgfB2a6MBx+AEVVlAHXRG1YYxLiBpPX2KhmoB99R5vA4b72iJrjle68NDWnbrE9Dg==} + '@opentelemetry/sdk-trace-base@1.30.1': + resolution: {integrity: sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/sdk-trace-node@1.30.0': - resolution: {integrity: sha512-MeXkXEdBs9xq1JSGTr/3P1lHBSUBaVmo1+UpoQhUpviPMzDXy0MNsdTC7KKI6/YcG74lTX6eqeNjlC1jV4Rstw==} + '@opentelemetry/sdk-trace-node@1.30.1': + resolution: {integrity: sha512-cBjYOINt1JxXdpw1e5MlHmFRc5fgj4GW/86vsKFxJCJ8AL4PdVtYH41gWwl4qd4uQjqEL1oJVrXkSy5cnduAnQ==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' @@ -1431,14 +1422,34 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@pnpm/catalogs.protocol-parser@1000.0.0': + resolution: {integrity: sha512-8eC25RAiu8BTaEseQmbo5xemlSwl06pMsUVORiYGX7JZEDb0UQVXOnbqFFJMPe/dyO8uwGXnDb350nauMzaraA==} + engines: {node: '>=18.12'} + + '@pnpm/catalogs.resolver@1000.0.1': + resolution: {integrity: sha512-A6KdGQMYNiwBe7N4CuQXfxhHnHo1MBX+d+EiAm+CEXeVrKPhm2RmVHRcKkF+pBlr6vV/KHp9z5tF2B3+jFnnRw==} + engines: {node: '>=18.12'} + + '@pnpm/catalogs.types@1000.0.0': + resolution: {integrity: sha512-xRf72lk7xHNvbenA4sp4Of/90QDdRW0CRYT+V+EbqpUXu1xsXtedHai34cTU6VGe7C1hUukxxE9eYTtIpYrx5g==} + engines: {node: '>=18.12'} + '@pnpm/config.env-replace@1.1.0': resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} engines: {node: '>=12.22.0'} + '@pnpm/constants@1001.0.0': + resolution: {integrity: sha512-yg+S/e8goJoUC4CxyKAplT2fuI94G/nynshPCNkLsi69bUMim5iDMuiiiMo/LeMoOjuyfByealD3u1mqNXrl+A==} + engines: {node: '>=18.12'} + '@pnpm/constants@6.1.0': resolution: {integrity: sha512-L6AiU3OXv9kjKGTJN9j8n1TeJGDcLX9atQlZvAkthlvbXjvKc5SKNWESc/eXhr5nEfuMWhQhiKHDJCpYejmeCQ==} engines: {node: '>=14.19'} + '@pnpm/error@1000.0.1': + resolution: {integrity: sha512-FdDMjp9ORXK/JlxwjlNF7mRohVKx5pvM01bxsVUSLFLZ9/oMBtDceWlntUTLhvr2MwYcr69mGilTojnXEbsjQA==} + engines: {node: '>=18.12'} + '@pnpm/error@4.0.0': resolution: {integrity: sha512-NI4DFCMF6xb1SA0bZiiV5KrMCaJM2QmPJFC6p78FXujn7FpiRSWhT9r032wpuQumsl7DEmN4s3wl/P8TA+bL8w==} engines: {node: '>=14.6'} @@ -1455,6 +1466,14 @@ packages: resolution: {integrity: sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==} engines: {node: '>=12'} + '@pnpm/parse-overrides@1000.0.1': + resolution: {integrity: sha512-dr7dBAFYsS2XS483/Z2bwk1ng8OwK4ixK3H7/RIptG1yD1GgYf4rZR1jAvQdmyuyTttKE9jSAdJLNFPo0bTRug==} + engines: {node: '>=18.12'} + + '@pnpm/parse-wanted-dependency@1000.0.0': + resolution: {integrity: sha512-SKK9m7leIQ0u6S+/LXREF0wTrFnyKiirLza6Dt0l7CL9pZdZtuI3mMvz6gNBFnIjTKJPwacdqRywT3bfK8W+FQ==} + engines: {node: '>=18.12'} + '@pnpm/read-project-manifest@4.1.1': resolution: {integrity: sha512-jGNoofG8kkUlgAMX8fqbUwRRXYf4WcWdvi/y1Sv1abUfcoVgXW6GdGVm0MIJ+enaong3hXHjaLl/AwmSj6O1Uw==} engines: {node: '>=14.6'} @@ -1550,8 +1569,8 @@ packages: '@renovatebot/osv-offline-db@1.7.0': resolution: {integrity: sha512-E24t9YfqJWkR2VbkUiatTEeUdWcTgVly/9JMAEYrLEjJPNZDQQEophsH19faOwFPnIoHhIpsNeTWf2iPhn4OEg==} - '@renovatebot/osv-offline@1.5.11': - resolution: {integrity: sha512-y4lTOL90P1JpsAQ+gadSCXmeRP+tvRrFPCmKtsRUwq/00vO2HoTi9GgKRTEWS6QxtDVnKlHrQS0xbBCmdXySTA==} + '@renovatebot/osv-offline@1.5.12': + resolution: {integrity: sha512-gmPsOjHoCsaIsahv+T373WDc28GZ5WTzcA6Usxd1lDGvEF/t7JdBe3lx1ZBLurbundnGmKJ3zYBsqmr54lBR7A==} '@renovatebot/pep440@4.0.1': resolution: {integrity: sha512-jKodfnFIIGjK9PcoB7+2JkDQ+prjv3LHFMUS21F3+IEaiGomrvpiH27+gjyQRRwtSkkRnrxkqjEPdkj2IxC2qA==} @@ -1644,271 +1663,280 @@ packages: '@sinonjs/text-encoding@0.7.3': resolution: {integrity: sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==} - '@smithy/abort-controller@3.1.9': - resolution: {integrity: sha512-yiW0WI30zj8ZKoSYNx90no7ugVn3khlyH/z5W8qtKBtVE6awRALbhSG+2SAHA1r6bO/6M9utxYKVZ3PCJ1rWxw==} - engines: {node: '>=16.0.0'} + '@smithy/abort-controller@4.0.1': + resolution: {integrity: sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g==} + engines: {node: '>=18.0.0'} - '@smithy/chunked-blob-reader-native@3.0.1': - resolution: {integrity: sha512-VEYtPvh5rs/xlyqpm5NRnfYLZn+q0SRPELbvBV+C/G7IQ+ouTuo+NKKa3ShG5OaFR8NYVMXls9hPYLTvIKKDrQ==} + '@smithy/chunked-blob-reader-native@4.0.0': + resolution: {integrity: sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==} + engines: {node: '>=18.0.0'} - '@smithy/chunked-blob-reader@4.0.0': - resolution: {integrity: sha512-jSqRnZvkT4egkq/7b6/QRCNXmmYVcHwnJldqJ3IhVpQE2atObVJ137xmGeuGFhjFUr8gCEVAOKwSY79OvpbDaQ==} + '@smithy/chunked-blob-reader@5.0.0': + resolution: {integrity: sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==} + engines: {node: '>=18.0.0'} - '@smithy/config-resolver@3.0.13': - resolution: {integrity: sha512-Gr/qwzyPaTL1tZcq8WQyHhTZREER5R1Wytmz4WnVGL4onA3dNk6Btll55c8Vr58pLdvWZmtG8oZxJTw3t3q7Jg==} - engines: {node: '>=16.0.0'} + '@smithy/config-resolver@4.0.1': + resolution: {integrity: sha512-Igfg8lKu3dRVkTSEm98QpZUvKEOa71jDX4vKRcvJVyRc3UgN3j7vFMf0s7xLQhYmKa8kyJGQgUJDOV5V3neVlQ==} + engines: {node: '>=18.0.0'} - '@smithy/core@2.5.7': - resolution: {integrity: sha512-8olpW6mKCa0v+ibCjoCzgZHQx1SQmZuW/WkrdZo73wiTprTH6qhmskT60QLFdT9DRa5mXxjz89kQPZ7ZSsoqqg==} - engines: {node: '>=16.0.0'} + '@smithy/core@3.1.1': + resolution: {integrity: sha512-hhUZlBWYuh9t6ycAcN90XOyG76C1AzwxZZgaCVPMYpWqqk9uMFo7HGG5Zu2cEhCJn7DdOi5krBmlibWWWPgdsw==} + engines: {node: '>=18.0.0'} - '@smithy/credential-provider-imds@3.2.8': - resolution: {integrity: sha512-ZCY2yD0BY+K9iMXkkbnjo+08T2h8/34oHd0Jmh6BZUSZwaaGlGCyBT/3wnS7u7Xl33/EEfN4B6nQr3Gx5bYxgw==} - engines: {node: '>=16.0.0'} + '@smithy/credential-provider-imds@4.0.1': + resolution: {integrity: sha512-l/qdInaDq1Zpznpmev/+52QomsJNZ3JkTl5yrTl02V6NBgJOQ4LY0SFw/8zsMwj3tLe8vqiIuwF6nxaEwgf6mg==} + engines: {node: '>=18.0.0'} - '@smithy/eventstream-codec@3.1.10': - resolution: {integrity: sha512-323B8YckSbUH0nMIpXn7HZsAVKHYHFUODa8gG9cHo0ySvA1fr5iWaNT+iIL0UCqUzG6QPHA3BSsBtRQou4mMqQ==} + '@smithy/eventstream-codec@4.0.1': + resolution: {integrity: sha512-Q2bCAAR6zXNVtJgifsU16ZjKGqdw/DyecKNgIgi7dlqw04fqDu0mnq+JmGphqheypVc64CYq3azSuCpAdFk2+A==} + engines: {node: '>=18.0.0'} - '@smithy/eventstream-serde-browser@3.0.14': - resolution: {integrity: sha512-kbrt0vjOIihW3V7Cqj1SXQvAI5BR8SnyQYsandva0AOR307cXAc+IhPngxIPslxTLfxwDpNu0HzCAq6g42kCPg==} - engines: {node: '>=16.0.0'} + '@smithy/eventstream-serde-browser@4.0.1': + resolution: {integrity: sha512-HbIybmz5rhNg+zxKiyVAnvdM3vkzjE6ccrJ620iPL8IXcJEntd3hnBl+ktMwIy12Te/kyrSbUb8UCdnUT4QEdA==} + engines: {node: '>=18.0.0'} - '@smithy/eventstream-serde-config-resolver@3.0.11': - resolution: {integrity: sha512-P2pnEp4n75O+QHjyO7cbw/vsw5l93K/8EWyjNCAAybYwUmj3M+hjSQZ9P5TVdUgEG08ueMAP5R4FkuSkElZ5tQ==} - engines: {node: '>=16.0.0'} + '@smithy/eventstream-serde-config-resolver@4.0.1': + resolution: {integrity: sha512-lSipaiq3rmHguHa3QFF4YcCM3VJOrY9oq2sow3qlhFY+nBSTF/nrO82MUQRPrxHQXA58J5G1UnU2WuJfi465BA==} + engines: {node: '>=18.0.0'} - '@smithy/eventstream-serde-node@3.0.13': - resolution: {integrity: sha512-zqy/9iwbj8Wysmvi7Lq7XFLeDgjRpTbCfwBhJa8WbrylTAHiAu6oQTwdY7iu2lxigbc9YYr9vPv5SzYny5tCXQ==} - engines: {node: '>=16.0.0'} + '@smithy/eventstream-serde-node@4.0.1': + resolution: {integrity: sha512-o4CoOI6oYGYJ4zXo34U8X9szDe3oGjmHgsMGiZM0j4vtNoT+h80TLnkUcrLZR3+E6HIxqW+G+9WHAVfl0GXK0Q==} + engines: {node: '>=18.0.0'} - '@smithy/eventstream-serde-universal@3.0.13': - resolution: {integrity: sha512-L1Ib66+gg9uTnqp/18Gz4MDpJPKRE44geOjOQ2SVc0eiaO5l255ADziATZgjQjqumC7yPtp1XnjHlF1srcwjKw==} - engines: {node: '>=16.0.0'} + '@smithy/eventstream-serde-universal@4.0.1': + resolution: {integrity: sha512-Z94uZp0tGJuxds3iEAZBqGU2QiaBHP4YytLUjwZWx+oUeohCsLyUm33yp4MMBmhkuPqSbQCXq5hDet6JGUgHWA==} + engines: {node: '>=18.0.0'} - '@smithy/fetch-http-handler@4.1.3': - resolution: {integrity: sha512-6SxNltSncI8s689nvnzZQc/dPXcpHQ34KUj6gR/HBroytKOd/isMG3gJF/zBE1TBmTT18TXyzhg3O3SOOqGEhA==} + '@smithy/fetch-http-handler@5.0.1': + resolution: {integrity: sha512-3aS+fP28urrMW2KTjb6z9iFow6jO8n3MFfineGbndvzGZit3taZhKWtTorf+Gp5RpFDDafeHlhfsGlDCXvUnJA==} + engines: {node: '>=18.0.0'} - '@smithy/hash-blob-browser@3.1.10': - resolution: {integrity: sha512-elwslXOoNunmfS0fh55jHggyhccobFkexLYC1ZeZ1xP2BTSrcIBaHV2b4xUQOdctrSNOpMqOZH1r2XzWTEhyfA==} + '@smithy/hash-blob-browser@4.0.1': + resolution: {integrity: sha512-rkFIrQOKZGS6i1D3gKJ8skJ0RlXqDvb1IyAphksaFOMzkn3v3I1eJ8m7OkLj0jf1McP63rcCEoLlkAn/HjcTRw==} + engines: {node: '>=18.0.0'} - '@smithy/hash-node@3.0.11': - resolution: {integrity: sha512-emP23rwYyZhQBvklqTtwetkQlqbNYirDiEEwXl2v0GYWMnCzxst7ZaRAnWuy28njp5kAH54lvkdG37MblZzaHA==} - engines: {node: '>=16.0.0'} + '@smithy/hash-node@4.0.1': + resolution: {integrity: sha512-TJ6oZS+3r2Xu4emVse1YPB3Dq3d8RkZDKcPr71Nj/lJsdAP1c7oFzYqEn1IBc915TsgLl2xIJNuxCz+gLbLE0w==} + engines: {node: '>=18.0.0'} - '@smithy/hash-stream-node@3.1.10': - resolution: {integrity: sha512-olomK/jZQ93OMayW1zfTHwcbwBdhcZOHsyWyiZ9h9IXvc1mCD/VuvzbLb3Gy/qNJwI4MANPLctTp2BucV2oU/Q==} - engines: {node: '>=16.0.0'} + '@smithy/hash-stream-node@4.0.1': + resolution: {integrity: sha512-U1rAE1fxmReCIr6D2o/4ROqAQX+GffZpyMt3d7njtGDr2pUNmAKRWa49gsNVhCh2vVAuf3wXzWwNr2YN8PAXIw==} + engines: {node: '>=18.0.0'} - '@smithy/invalid-dependency@3.0.11': - resolution: {integrity: sha512-NuQmVPEJjUX6c+UELyVz8kUx8Q539EDeNwbRyu4IIF8MeV7hUtq1FB3SHVyki2u++5XLMFqngeMKk7ccspnNyQ==} + '@smithy/invalid-dependency@4.0.1': + resolution: {integrity: sha512-gdudFPf4QRQ5pzj7HEnu6FhKRi61BfH/Gk5Yf6O0KiSbr1LlVhgjThcvjdu658VE6Nve8vaIWB8/fodmS1rBPQ==} + engines: {node: '>=18.0.0'} '@smithy/is-array-buffer@2.2.0': resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} engines: {node: '>=14.0.0'} - '@smithy/is-array-buffer@3.0.0': - resolution: {integrity: sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==} - engines: {node: '>=16.0.0'} + '@smithy/is-array-buffer@4.0.0': + resolution: {integrity: sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==} + engines: {node: '>=18.0.0'} - '@smithy/md5-js@3.0.11': - resolution: {integrity: sha512-3NM0L3i2Zm4bbgG6Ymi9NBcxXhryi3uE8fIfHJZIOfZVxOkGdjdgjR9A06SFIZCfnEIWKXZdm6Yq5/aPXFFhsQ==} + '@smithy/md5-js@4.0.1': + resolution: {integrity: sha512-HLZ647L27APi6zXkZlzSFZIjpo8po45YiyjMGJZM3gyDY8n7dPGdmxIIljLm4gPt/7rRvutLTTkYJpZVfG5r+A==} + engines: {node: '>=18.0.0'} - '@smithy/middleware-content-length@3.0.13': - resolution: {integrity: sha512-zfMhzojhFpIX3P5ug7jxTjfUcIPcGjcQYzB9t+rv0g1TX7B0QdwONW+ATouaLoD7h7LOw/ZlXfkq4xJ/g2TrIw==} - engines: {node: '>=16.0.0'} + '@smithy/middleware-content-length@4.0.1': + resolution: {integrity: sha512-OGXo7w5EkB5pPiac7KNzVtfCW2vKBTZNuCctn++TTSOMpe6RZO/n6WEC1AxJINn3+vWLKW49uad3lo/u0WJ9oQ==} + engines: {node: '>=18.0.0'} - '@smithy/middleware-endpoint@3.2.8': - resolution: {integrity: sha512-OEJZKVUEhMOqMs3ktrTWp7UvvluMJEvD5XgQwRePSbDg1VvBaL8pX8mwPltFn6wk1GySbcVwwyldL8S+iqnrEQ==} - engines: {node: '>=16.0.0'} + '@smithy/middleware-endpoint@4.0.2': + resolution: {integrity: sha512-Z9m67CXizGpj8CF/AW/7uHqYNh1VXXOn9Ap54fenWsCa0HnT4cJuE61zqG3cBkTZJDCy0wHJphilI41co/PE5g==} + engines: {node: '>=18.0.0'} - '@smithy/middleware-retry@3.0.34': - resolution: {integrity: sha512-yVRr/AAtPZlUvwEkrq7S3x7Z8/xCd97m2hLDaqdz6ucP2RKHsBjEqaUA2ebNv2SsZoPEi+ZD0dZbOB1u37tGCA==} - engines: {node: '>=16.0.0'} + '@smithy/middleware-retry@4.0.3': + resolution: {integrity: sha512-TiKwwQTwUDeDtwWW8UWURTqu7s6F3wN2pmziLU215u7bqpVT9Mk2oEvURjpRLA+5XeQhM68R5BpAGzVtomsqgA==} + engines: {node: '>=18.0.0'} - '@smithy/middleware-serde@3.0.11': - resolution: {integrity: sha512-KzPAeySp/fOoQA82TpnwItvX8BBURecpx6ZMu75EZDkAcnPtO6vf7q4aH5QHs/F1s3/snQaSFbbUMcFFZ086Mw==} - engines: {node: '>=16.0.0'} + '@smithy/middleware-serde@4.0.1': + resolution: {integrity: sha512-Fh0E2SOF+S+P1+CsgKyiBInAt3o2b6Qk7YOp2W0Qx2XnfTdfMuSDKUEcnrtpxCzgKJnqXeLUZYqtThaP0VGqtA==} + engines: {node: '>=18.0.0'} - '@smithy/middleware-stack@3.0.11': - resolution: {integrity: sha512-1HGo9a6/ikgOMrTrWL/WiN9N8GSVYpuRQO5kjstAq4CvV59bjqnh7TbdXGQ4vxLD3xlSjfBjq5t1SOELePsLnA==} - engines: {node: '>=16.0.0'} + '@smithy/middleware-stack@4.0.1': + resolution: {integrity: sha512-dHwDmrtR/ln8UTHpaIavRSzeIk5+YZTBtLnKwDW3G2t6nAupCiQUvNzNoHBpik63fwUaJPtlnMzXbQrNFWssIA==} + engines: {node: '>=18.0.0'} - '@smithy/node-config-provider@3.1.12': - resolution: {integrity: sha512-O9LVEu5J/u/FuNlZs+L7Ikn3lz7VB9hb0GtPT9MQeiBmtK8RSY3ULmsZgXhe6VAlgTw0YO+paQx4p8xdbs43vQ==} - engines: {node: '>=16.0.0'} + '@smithy/node-config-provider@4.0.1': + resolution: {integrity: sha512-8mRTjvCtVET8+rxvmzRNRR0hH2JjV0DFOmwXPrISmTIJEfnCBugpYYGAsCj8t41qd+RB5gbheSQ/6aKZCQvFLQ==} + engines: {node: '>=18.0.0'} - '@smithy/node-http-handler@3.3.3': - resolution: {integrity: sha512-BrpZOaZ4RCbcJ2igiSNG16S+kgAc65l/2hmxWdmhyoGWHTLlzQzr06PXavJp9OBlPEG/sHlqdxjWmjzV66+BSQ==} - engines: {node: '>=16.0.0'} + '@smithy/node-http-handler@4.0.2': + resolution: {integrity: sha512-X66H9aah9hisLLSnGuzRYba6vckuFtGE+a5DcHLliI/YlqKrGoxhisD5XbX44KyoeRzoNlGr94eTsMVHFAzPOw==} + engines: {node: '>=18.0.0'} - '@smithy/property-provider@3.1.11': - resolution: {integrity: sha512-I/+TMc4XTQ3QAjXfOcUWbSS073oOEAxgx4aZy8jHaf8JQnRkq2SZWw8+PfDtBvLUjcGMdxl+YwtzWe6i5uhL/A==} - engines: {node: '>=16.0.0'} + '@smithy/property-provider@4.0.1': + resolution: {integrity: sha512-o+VRiwC2cgmk/WFV0jaETGOtX16VNPp2bSQEzu0whbReqE1BMqsP2ami2Vi3cbGVdKu1kq9gQkDAGKbt0WOHAQ==} + engines: {node: '>=18.0.0'} - '@smithy/protocol-http@4.1.8': - resolution: {integrity: sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==} - engines: {node: '>=16.0.0'} + '@smithy/protocol-http@5.0.1': + resolution: {integrity: sha512-TE4cpj49jJNB/oHyh/cRVEgNZaoPaxd4vteJNB0yGidOCVR0jCw/hjPVsT8Q8FRmj8Bd3bFZt8Dh7xGCT+xMBQ==} + engines: {node: '>=18.0.0'} - '@smithy/querystring-builder@3.0.11': - resolution: {integrity: sha512-u+5HV/9uJaeLj5XTb6+IEF/dokWWkEqJ0XiaRRogyREmKGUgZnNecLucADLdauWFKUNbQfulHFEZEdjwEBjXRg==} - engines: {node: '>=16.0.0'} + '@smithy/querystring-builder@4.0.1': + resolution: {integrity: sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg==} + engines: {node: '>=18.0.0'} - '@smithy/querystring-parser@3.0.11': - resolution: {integrity: sha512-Je3kFvCsFMnso1ilPwA7GtlbPaTixa3WwC+K21kmMZHsBEOZYQaqxcMqeFFoU7/slFjKDIpiiPydvdJm8Q/MCw==} - engines: {node: '>=16.0.0'} + '@smithy/querystring-parser@4.0.1': + resolution: {integrity: sha512-Ma2XC7VS9aV77+clSFylVUnPZRindhB7BbmYiNOdr+CHt/kZNJoPP0cd3QxCnCFyPXC4eybmyE98phEHkqZ5Jw==} + engines: {node: '>=18.0.0'} - '@smithy/service-error-classification@3.0.11': - resolution: {integrity: sha512-QnYDPkyewrJzCyaeI2Rmp7pDwbUETe+hU8ADkXmgNusO1bgHBH7ovXJiYmba8t0fNfJx75fE8dlM6SEmZxheog==} - engines: {node: '>=16.0.0'} + '@smithy/service-error-classification@4.0.1': + resolution: {integrity: sha512-3JNjBfOWpj/mYfjXJHB4Txc/7E4LVq32bwzE7m28GN79+M1f76XHflUaSUkhOriprPDzev9cX/M+dEB80DNDKA==} + engines: {node: '>=18.0.0'} - '@smithy/shared-ini-file-loader@3.1.12': - resolution: {integrity: sha512-1xKSGI+U9KKdbG2qDvIR9dGrw3CNx+baqJfyr0igKEpjbHL5stsqAesYBzHChYHlelWtb87VnLWlhvfCz13H8Q==} - engines: {node: '>=16.0.0'} + '@smithy/shared-ini-file-loader@4.0.1': + resolution: {integrity: sha512-hC8F6qTBbuHRI/uqDgqqi6J0R4GtEZcgrZPhFQnMhfJs3MnUTGSnR1NSJCJs5VWlMydu0kJz15M640fJlRsIOw==} + engines: {node: '>=18.0.0'} - '@smithy/signature-v4@4.2.4': - resolution: {integrity: sha512-5JWeMQYg81TgU4cG+OexAWdvDTs5JDdbEZx+Qr1iPbvo91QFGzjy0IkXAKaXUHqmKUJgSHK0ZxnCkgZpzkeNTA==} - engines: {node: '>=16.0.0'} + '@smithy/signature-v4@5.0.1': + resolution: {integrity: sha512-nCe6fQ+ppm1bQuw5iKoeJ0MJfz2os7Ic3GBjOkLOPtavbD1ONoyE3ygjBfz2ythFWm4YnRm6OxW+8p/m9uCoIA==} + engines: {node: '>=18.0.0'} - '@smithy/smithy-client@3.7.0': - resolution: {integrity: sha512-9wYrjAZFlqWhgVo3C4y/9kpc68jgiSsKUnsFPzr/MSiRL93+QRDafGTfhhKAb2wsr69Ru87WTiqSfQusSmWipA==} - engines: {node: '>=16.0.0'} + '@smithy/smithy-client@4.1.2': + resolution: {integrity: sha512-0yApeHWBqocelHGK22UivZyShNxFbDNrgREBllGh5Ws0D0rg/yId/CJfeoKKpjbfY2ju8j6WgDUGZHYQmINZ5w==} + engines: {node: '>=18.0.0'} - '@smithy/types@3.7.2': - resolution: {integrity: sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==} - engines: {node: '>=16.0.0'} + '@smithy/types@4.1.0': + resolution: {integrity: sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==} + engines: {node: '>=18.0.0'} - '@smithy/url-parser@3.0.11': - resolution: {integrity: sha512-TmlqXkSk8ZPhfc+SQutjmFr5FjC0av3GZP4B/10caK1SbRwe/v+Wzu/R6xEKxoNqL+8nY18s1byiy6HqPG37Aw==} + '@smithy/url-parser@4.0.1': + resolution: {integrity: sha512-gPXcIEUtw7VlK8f/QcruNXm7q+T5hhvGu9tl63LsJPZ27exB6dtNwvh2HIi0v7JcXJ5emBxB+CJxwaLEdJfA+g==} + engines: {node: '>=18.0.0'} - '@smithy/util-base64@3.0.0': - resolution: {integrity: sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==} - engines: {node: '>=16.0.0'} + '@smithy/util-base64@4.0.0': + resolution: {integrity: sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==} + engines: {node: '>=18.0.0'} - '@smithy/util-body-length-browser@3.0.0': - resolution: {integrity: sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==} + '@smithy/util-body-length-browser@4.0.0': + resolution: {integrity: sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==} + engines: {node: '>=18.0.0'} - '@smithy/util-body-length-node@3.0.0': - resolution: {integrity: sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==} - engines: {node: '>=16.0.0'} + '@smithy/util-body-length-node@4.0.0': + resolution: {integrity: sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==} + engines: {node: '>=18.0.0'} '@smithy/util-buffer-from@2.2.0': resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} engines: {node: '>=14.0.0'} - '@smithy/util-buffer-from@3.0.0': - resolution: {integrity: sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==} - engines: {node: '>=16.0.0'} + '@smithy/util-buffer-from@4.0.0': + resolution: {integrity: sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==} + engines: {node: '>=18.0.0'} - '@smithy/util-config-provider@3.0.0': - resolution: {integrity: sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==} - engines: {node: '>=16.0.0'} + '@smithy/util-config-provider@4.0.0': + resolution: {integrity: sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==} + engines: {node: '>=18.0.0'} - '@smithy/util-defaults-mode-browser@3.0.34': - resolution: {integrity: sha512-FumjjF631lR521cX+svMLBj3SwSDh9VdtyynTYDAiBDEf8YPP5xORNXKQ9j0105o5+ARAGnOOP/RqSl40uXddA==} - engines: {node: '>= 10.0.0'} + '@smithy/util-defaults-mode-browser@4.0.3': + resolution: {integrity: sha512-7c5SF1fVK0EOs+2EOf72/qF199zwJflU1d02AevwKbAUPUZyE9RUZiyJxeUmhVxfKDWdUKaaVojNiaDQgnHL9g==} + engines: {node: '>=18.0.0'} - '@smithy/util-defaults-mode-node@3.0.34': - resolution: {integrity: sha512-vN6aHfzW9dVVzkI0wcZoUXvfjkl4CSbM9nE//08lmUMyf00S75uuCpTrqF9uD4bD9eldIXlt53colrlwKAT8Gw==} - engines: {node: '>= 10.0.0'} + '@smithy/util-defaults-mode-node@4.0.3': + resolution: {integrity: sha512-CVnD42qYD3JKgDlImZ9+On+MqJHzq9uJgPbMdeBE8c2x8VJ2kf2R3XO/yVFx+30ts5lD/GlL0eFIShY3x9ROgQ==} + engines: {node: '>=18.0.0'} - '@smithy/util-endpoints@2.1.7': - resolution: {integrity: sha512-tSfcqKcN/Oo2STEYCABVuKgJ76nyyr6skGl9t15hs+YaiU06sgMkN7QYjo0BbVw+KT26zok3IzbdSOksQ4YzVw==} - engines: {node: '>=16.0.0'} + '@smithy/util-endpoints@3.0.1': + resolution: {integrity: sha512-zVdUENQpdtn9jbpD9SCFK4+aSiavRb9BxEtw9ZGUR1TYo6bBHbIoi7VkrFQ0/RwZlzx0wRBaRmPclj8iAoJCLA==} + engines: {node: '>=18.0.0'} - '@smithy/util-hex-encoding@3.0.0': - resolution: {integrity: sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==} - engines: {node: '>=16.0.0'} + '@smithy/util-hex-encoding@4.0.0': + resolution: {integrity: sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==} + engines: {node: '>=18.0.0'} - '@smithy/util-middleware@3.0.11': - resolution: {integrity: sha512-dWpyc1e1R6VoXrwLoLDd57U1z6CwNSdkM69Ie4+6uYh2GC7Vg51Qtan7ITzczuVpqezdDTKJGJB95fFvvjU/ow==} - engines: {node: '>=16.0.0'} + '@smithy/util-middleware@4.0.1': + resolution: {integrity: sha512-HiLAvlcqhbzhuiOa0Lyct5IIlyIz0PQO5dnMlmQ/ubYM46dPInB+3yQGkfxsk6Q24Y0n3/JmcA1v5iEhmOF5mA==} + engines: {node: '>=18.0.0'} - '@smithy/util-retry@3.0.11': - resolution: {integrity: sha512-hJUC6W7A3DQgaee3Hp9ZFcOxVDZzmBIRBPlUAk8/fSOEl7pE/aX7Dci0JycNOnm9Mfr0KV2XjIlUOcGWXQUdVQ==} - engines: {node: '>=16.0.0'} + '@smithy/util-retry@4.0.1': + resolution: {integrity: sha512-WmRHqNVwn3kI3rKk1LsKcVgPBG6iLTBGC1iYOV3GQegwJ3E8yjzHytPt26VNzOWr1qu0xE03nK0Ug8S7T7oufw==} + engines: {node: '>=18.0.0'} - '@smithy/util-stream@3.3.4': - resolution: {integrity: sha512-SGhGBG/KupieJvJSZp/rfHHka8BFgj56eek9px4pp7lZbOF+fRiVr4U7A3y3zJD8uGhxq32C5D96HxsTC9BckQ==} - engines: {node: '>=16.0.0'} + '@smithy/util-stream@4.0.2': + resolution: {integrity: sha512-0eZ4G5fRzIoewtHtwaYyl8g2C+osYOT4KClXgfdNEDAgkbe2TYPqcnw4GAWabqkZCax2ihRGPe9LZnsPdIUIHA==} + engines: {node: '>=18.0.0'} - '@smithy/util-uri-escape@3.0.0': - resolution: {integrity: sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==} - engines: {node: '>=16.0.0'} + '@smithy/util-uri-escape@4.0.0': + resolution: {integrity: sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==} + engines: {node: '>=18.0.0'} '@smithy/util-utf8@2.3.0': resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} engines: {node: '>=14.0.0'} - '@smithy/util-utf8@3.0.0': - resolution: {integrity: sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==} - engines: {node: '>=16.0.0'} + '@smithy/util-utf8@4.0.0': + resolution: {integrity: sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==} + engines: {node: '>=18.0.0'} - '@smithy/util-waiter@3.2.0': - resolution: {integrity: sha512-PpjSboaDUE6yl+1qlg3Si57++e84oXdWGbuFUSAciXsVfEZJJJupR2Nb0QuXHiunt2vGR+1PTizOMvnUPaG2Qg==} - engines: {node: '>=16.0.0'} + '@smithy/util-waiter@4.0.2': + resolution: {integrity: sha512-piUTHyp2Axx3p/kc2CIJkYSv0BAaheBQmbACZgQSSfWUumWNW+R1lL+H9PDBxKJkvOeEX+hKYEFiwO8xagL8AQ==} + engines: {node: '>=18.0.0'} - '@swc/core-darwin-arm64@1.10.4': - resolution: {integrity: sha512-sV/eurLhkjn/197y48bxKP19oqcLydSel42Qsy2zepBltqUx+/zZ8+/IS0Bi7kaWVFxerbW1IPB09uq8Zuvm3g==} + '@swc/core-darwin-arm64@1.10.9': + resolution: {integrity: sha512-XTHLtijFervv2B+i1ngM993umhSj9K1IeMomvU/Db84Asjur2XmD4KXt9QPnGDRFgv2kLSjZ+DDL25Qk0f4r+w==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.10.4': - resolution: {integrity: sha512-gjYNU6vrAUO4+FuovEo9ofnVosTFXkF0VDuo1MKPItz6e2pxc2ale4FGzLw0Nf7JB1sX4a8h06CN16/pLJ8Q2w==} + '@swc/core-darwin-x64@1.10.9': + resolution: {integrity: sha512-bi3el9/FV/la8HIsolSjeDar+tM7m9AmSF1w7X6ZByW2qgc4Z1tmq0A4M4H9aH3TfHesZbfq8hgaNtc2/VtzzQ==} engines: {node: '>=10'} cpu: [x64] os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.10.4': - resolution: {integrity: sha512-zd7fXH5w8s+Sfvn2oO464KDWl+ZX1MJiVmE4Pdk46N3PEaNwE0koTfgx2vQRqRG4vBBobzVvzICC3618WcefOA==} + '@swc/core-linux-arm-gnueabihf@1.10.9': + resolution: {integrity: sha512-xsLHV02S+RTDuI+UJBkA2muNk/s0ETRpoc1K/gNt0i8BqTurPYkrvGDDALN9+leiUPydHvZi9P1qdExbgUJnXw==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.10.4': - resolution: {integrity: sha512-+UGfoHDxsMZgFD3tABKLeEZHqLNOkxStu+qCG7atGBhS4Slri6h6zijVvf4yI5X3kbXdvc44XV/hrP/Klnui2A==} + '@swc/core-linux-arm64-gnu@1.10.9': + resolution: {integrity: sha512-41hJgPoGhIa12U6Tud+yLF/m64YA3mGut3TmBEkj2R7rdJdE0mljdtR0tf4J2RoQaWZPPi0DBSqGdROiAEx9dg==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-arm64-musl@1.10.4': - resolution: {integrity: sha512-cDDj2/uYsOH0pgAnDkovLZvKJpFmBMyXkxEG6Q4yw99HbzO6QzZ5HDGWGWVq/6dLgYKlnnmpjZCPPQIu01mXEg==} + '@swc/core-linux-arm64-musl@1.10.9': + resolution: {integrity: sha512-DUMRhl49b9r7bLg9oNzCdW4lLcDJKrRBn87Iq5APPvixsm1auGnsVQycGkQcDDKvVllxIFSbmCYzjagx3l8Hnw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-x64-gnu@1.10.4': - resolution: {integrity: sha512-qJXh9D6Kf5xSdGWPINpLGixAbB5JX8JcbEJpRamhlDBoOcQC79dYfOMEIxWPhTS1DGLyFakAx2FX/b2VmQmj0g==} + '@swc/core-linux-x64-gnu@1.10.9': + resolution: {integrity: sha512-xW0y88vQvmzYo3Gn7yFnY03TfHMwuca4aFH3ZmhwDNOYHmTOi6fmhAkg/13F/NrwjMYO+GnF5uJTjdjb3B6tdQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-linux-x64-musl@1.10.4': - resolution: {integrity: sha512-A76lIAeyQnHCVt0RL/pG+0er8Qk9+acGJqSZOZm67Ve3B0oqMd871kPtaHBM0BW3OZAhoILgfHW3Op9Q3mx3Cw==} + '@swc/core-linux-x64-musl@1.10.9': + resolution: {integrity: sha512-jYs32BEx+CPVuxN6NdsWEpdehjnmAag25jyJzwjQx+NCGYwHEV3bT5y8TX4eFhaVB1rafmqJOlYQPs4+MSyGCg==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-win32-arm64-msvc@1.10.4': - resolution: {integrity: sha512-e6j5kBu4fIY7fFxFxnZI0MlEovRvp50Lg59Fw+DVbtqHk3C85dckcy5xKP+UoXeuEmFceauQDczUcGs19SRGSQ==} + '@swc/core-win32-arm64-msvc@1.10.9': + resolution: {integrity: sha512-Uhh5T3Fq3Nyom96Bm3ACBNASH3iqNc76in7ewZz8PooUqeTIO8aZpsghnncjctRNE9T819/8btpiFIhHo3sKtg==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.10.4': - resolution: {integrity: sha512-RSYHfdKgNXV/amY5Tqk1EWVsyQnhlsM//jeqMLw5Fy9rfxP592W9UTumNikNRPdjI8wKKzNMXDb1U29tQjN0dg==} + '@swc/core-win32-ia32-msvc@1.10.9': + resolution: {integrity: sha512-bD5BpbojEsDfrAvT+1qjQPf5RCKLg4UL+3Uwm019+ZR02hd8qO538BlOnQdOqRqccu+75DF6aRglQ7AJ24Cs0Q==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.10.4': - resolution: {integrity: sha512-1ujYpaqfqNPYdwKBlvJnOqcl+Syn3UrQ4XE0Txz6zMYgyh6cdU6a3pxqLqIUSJ12MtXRA9ZUhEz1ekU3LfLWXw==} + '@swc/core-win32-x64-msvc@1.10.9': + resolution: {integrity: sha512-NwkuUNeBBQnAaXVvcGw8Zr6RR8kylyjFUnlYZZ3G0QkQZ4rYLXYTafAmiRjrfzgVb0LcMF/sBzJvGOk7SwtIDg==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.10.4': - resolution: {integrity: sha512-ut3zfiTLORMxhr6y/GBxkHmzcGuVpwJYX4qyXWuBKkpw/0g0S5iO1/wW7RnLnZbAi8wS/n0atRZoaZlXWBkeJg==} + '@swc/core@1.10.9': + resolution: {integrity: sha512-MQ97YSXu2oibzm7wi4GNa7hhndjLuVt/lmO2sq53+P37oZmyg/JQ/IYYtSiC6UGK3+cHoiVAykrK+glxLjJbag==} engines: {node: '>=10'} peerDependencies: '@swc/helpers': '*' @@ -2019,11 +2047,11 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} - '@types/diff@6.0.0': - resolution: {integrity: sha512-dhVCYGv3ZSbzmQaBSagrv1WJ6rXCdkyTcDyoNu1MD8JohI7pR7k8wdZEm+mvdxRKXyHVwckFzWU1vJc+Z29MlA==} + '@types/diff@7.0.0': + resolution: {integrity: sha512-sVpkpbnTJL9CYoDf4U+tHaQLe5HiTaHWY7m9FuYA7oMCHwC9ie0Vh9eIGapyzYrU3+pILlSY2fAc4elfw5m4dg==} - '@types/emscripten@1.39.13': - resolution: {integrity: sha512-cFq+fO/isvhvmuP/+Sl4K4jtU6E23DoivtbO4r50e3odaxAiVdbfSYRDdJ4gCdxx+3aRjhphS5ZMwIH4hFy/Cw==} + '@types/emscripten@1.40.0': + resolution: {integrity: sha512-MD2JJ25S4tnjnhjWyalMS6K6p0h+zQV6+Ylm+aGbiS8tSn/aHLSGNzBgduj6FB4zH0ax2GRMGYi/8G1uOxhXWA==} '@types/eslint@8.56.12': resolution: {integrity: sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==} @@ -2088,8 +2116,8 @@ packages: '@types/linkify-markdown@1.0.3': resolution: {integrity: sha512-BnuGqDmpzmXCDMXHzgle/vMRUnbFcWclts0+n7Or421exav3XG6efl9gsxamLET6QPhX+pMnxcsHgnAO/daj9w==} - '@types/lodash@4.17.13': - resolution: {integrity: sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==} + '@types/lodash@4.17.14': + resolution: {integrity: sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==} '@types/luxon@3.4.2': resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==} @@ -2112,17 +2140,17 @@ packages: '@types/minimist@1.2.5': resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + '@types/moo@0.5.10': + resolution: {integrity: sha512-W6KzyZjXUYpwQfLK1O1UDzqcqYlul+lO7Bt71luyIIyNlOZwJaNeWWdqFs1C/f2hohZvUFHMk6oFNe9Rg48DbA==} + '@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@20.17.11': - resolution: {integrity: sha512-Ept5glCK35R8yeyIeYlRIZtX6SLRyqMhOFTgj5SOkMpLTdw3SEHI9fHx60xaUZ+V1aJxQJODE+7/j5ocZydYTg==} + '@types/node@22.10.9': + resolution: {integrity: sha512-Ir6hwgsKyNESl/gLOcEz3krR4CBGgliDqBQ2ma4wIhEx0w+xnoeTq3tdrNw15kU3SxogDjOgv9sqdtLW8mIHaw==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -2193,8 +2221,8 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@typescript-eslint/eslint-plugin@8.19.0': - resolution: {integrity: sha512-NggSaEZCdSrFddbctrVjkVZvFC6KGfKfNK0CU7mNK/iKHGKbzT4Wmgm08dKpcZECBu9f5FypndoMyRHkdqfT1Q==} + '@typescript-eslint/eslint-plugin@8.20.0': + resolution: {integrity: sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 @@ -2207,8 +2235,8 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - '@typescript-eslint/parser@8.19.0': - resolution: {integrity: sha512-6M8taKyOETY1TKHp0x8ndycipTVgmp4xtg5QpEZzXxDhNvvHOJi5rLRkLr8SK3jTgD5l4fTlvBiRdfsuWydxBw==} + '@typescript-eslint/parser@8.20.0': + resolution: {integrity: sha512-gKXG7A5HMyjDIedBi6bUrDcun8GIjnI8qOwVLiY3rx6T/sHP/19XLJOnIq/FgQvWLHja5JN/LSE7eklNBr612g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -2218,12 +2246,16 @@ packages: resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@typescript-eslint/scope-manager@8.19.0': - resolution: {integrity: sha512-hkoJiKQS3GQ13TSMEiuNmSCvhz7ujyqD1x3ShbaETATHrck+9RaDdUbt+osXaUuns9OFwrDTTrjtwsU8gJyyRA==} + '@typescript-eslint/scope-manager@8.20.0': + resolution: {integrity: sha512-J7+VkpeGzhOt3FeG1+SzhiMj9NzGD/M6KoGn9f4dbz3YzK9hvbhVTmLj/HiTp9DazIzJ8B4XcM80LrR9Dm1rJw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/scope-manager@8.21.0': + resolution: {integrity: sha512-G3IBKz0/0IPfdeGRMbp+4rbjfSSdnGkXsM/pFZA8zM9t9klXDnB/YnKOBQ0GoPmoROa4bCq2NeHgJa5ydsQ4mA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.19.0': - resolution: {integrity: sha512-TZs0I0OSbd5Aza4qAMpp1cdCYVnER94IziudE3JU328YUHgWu9gwiwhag+fuLeJ2LkWLXI+F/182TbG+JaBdTg==} + '@typescript-eslint/type-utils@8.20.0': + resolution: {integrity: sha512-bPC+j71GGvA7rVNAHAtOjbVXbLN5PkwqMvy1cwGeaxUoRQXVuKCebRoLzm+IPW/NtFFpstn1ummSIasD5t60GA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -2233,8 +2265,12 @@ packages: resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@typescript-eslint/types@8.19.0': - resolution: {integrity: sha512-8XQ4Ss7G9WX8oaYvD4OOLCjIQYgRQxO+qCiR2V2s2GxI9AUpo7riNwo6jDhKtTcaJjT8PY54j2Yb33kWtSJsmA==} + '@typescript-eslint/types@8.20.0': + resolution: {integrity: sha512-cqaMiY72CkP+2xZRrFt3ExRBu0WmVitN/rYPZErA80mHjHx/Svgp8yfbzkJmDoQ/whcytOPO9/IZXnOc+wigRA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/types@8.21.0': + resolution: {integrity: sha512-PAL6LUuQwotLW2a8VsySDBwYMm129vFm4tMVlylzdoTybTHaAi0oBp7Ac6LhSrHHOdLM3efH+nAR6hAWoMF89A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript-eslint/typescript-estree@5.62.0': @@ -2246,8 +2282,14 @@ packages: typescript: optional: true - '@typescript-eslint/typescript-estree@8.19.0': - resolution: {integrity: sha512-WW9PpDaLIFW9LCbucMSdYUuGeFUz1OkWYS/5fwZwTA+l2RwlWFdJvReQqMUMBw4yJWJOfqd7An9uwut2Oj8sLw==} + '@typescript-eslint/typescript-estree@8.20.0': + resolution: {integrity: sha512-Y7ncuy78bJqHI35NwzWol8E0X7XkRVS4K4P4TCyzWkOJih5NDvtoRDW4Ba9YJJoB2igm9yXDdYI/+fkiiAxPzA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/typescript-estree@8.21.0': + resolution: {integrity: sha512-x+aeKh/AjAArSauz0GiQZsjT8ciadNMHdkUSwBB9Z6PrKc/4knM4g3UfHml6oDJmKC88a6//cdxnO/+P2LkMcg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <5.8.0' @@ -2258,8 +2300,15 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - '@typescript-eslint/utils@8.19.0': - resolution: {integrity: sha512-PTBG+0oEMPH9jCZlfg07LCB2nYI0I317yyvXGfxnvGvw4SHIOuRnQ3kadyyXY6tGdChusIHIbM5zfIbp4M6tCg==} + '@typescript-eslint/utils@8.20.0': + resolution: {integrity: sha512-dq70RUw6UK9ei7vxc4KQtBRk7qkHZv447OUZ6RPQMQl71I3NZxQJX/f32Smr+iqWrB02pHKn2yAdHBb0KNrRMA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/utils@8.21.0': + resolution: {integrity: sha512-xcXBfcq0Kaxgj7dwejMbFyq7IOHgpNMtVuDveK7w3ZGwG9owKzhALVwKpTF2yrZmEwl9SWdetf3fxNzJQaVuxw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -2269,12 +2318,16 @@ packages: resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@typescript-eslint/visitor-keys@8.19.0': - resolution: {integrity: sha512-mCFtBbFBJDCNCWUl5y6sZSCHXw1DEFEk3c/M3nRK2a4XUB8StGFtmcEMizdjKuBzB6e/smJAAWYug3VrdLMr1w==} + '@typescript-eslint/visitor-keys@8.20.0': + resolution: {integrity: sha512-v/BpkeeYAsPkKCkR8BDwcno0llhzWVqPOamQrAEMdpZav2Y9OVjd9dwJyBLJWwf335B5DmlifECIkZRJCaGaHA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ungap/structured-clone@1.2.1': - resolution: {integrity: sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==} + '@typescript-eslint/visitor-keys@8.21.0': + resolution: {integrity: sha512-BkLMNpdV6prozk8LlyK/SOoWLmUFi+ZD+pcqti9ILCbVvHGk1ui1g4jJOc2WDLaeExz2qWwojxlPce5PljcT3w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} '@yarnpkg/core@4.2.0': resolution: {integrity: sha512-h+cjnATkpO0ya6I5U4RYvOet/IsswOje3eBq9CsFM4XJZ2nS4WBBeFwYe0tqLD87IwKsoyuIdUwZjPHcn2DM8g==} @@ -2438,6 +2491,10 @@ packages: resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} engines: {node: '>=0.10.0'} + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + async-mutex@0.5.0: resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==} @@ -2504,8 +2561,8 @@ packages: before-after-hook@3.0.2: resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} - better-sqlite3@11.7.0: - resolution: {integrity: sha512-mXpa5jnIKKHeoGzBrUJrc65cXFKcILGZpU3FXR0pradUEm9MA7UZz02qfEejaMcm9iXrSOCenwwYMJ/tZ1y5Ig==} + better-sqlite3@11.8.1: + resolution: {integrity: sha512-9BxNaBkblMjhJW8sMRZxnxVTRgbRmssZW0Oxc1MPBTfiR+WW21e2Mk4qu8CzrcZb1LwPCnFsfDEzq+SNcBU8eg==} bignumber.js@9.1.2: resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==} @@ -2542,8 +2599,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.24.3: - resolution: {integrity: sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==} + browserslist@4.24.4: + resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -2566,6 +2623,9 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + builtins@5.1.0: + resolution: {integrity: sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==} + bunyan@1.8.15: resolution: {integrity: sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==} engines: {'0': node >=0.10.0} @@ -2625,8 +2685,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001690: - resolution: {integrity: sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==} + caniuse-lite@1.0.30001695: + resolution: {integrity: sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==} chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} @@ -2757,8 +2817,8 @@ packages: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} - commander@12.1.0: - resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + commander@13.1.0: + resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} engines: {node: '>=18'} commander@8.3.0: @@ -2818,8 +2878,8 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - core-js-pure@3.39.0: - resolution: {integrity: sha512-7fEcWwKI4rJinnK+wLTezeg2smbFFdSBP6E2kQZNbnzM2s1rpKQ6aaRteZSSg7FLU3P0HGGVo/gbpfanU36urg==} + core-js-pure@3.40.0: + resolution: {integrity: sha512-AtDzVIgRrmRKQai62yuSIN5vNiQjcJakJb4fbhVw3ehxx7Lohphvw9SGNWKhLFqSxC4ilD0g/L1huAYFQU3Q6A==} core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -2845,8 +2905,8 @@ packages: resolution: {integrity: sha512-onMB0OkDjkXunhdW9htFjEhqrD54+M94i6ackoUkjHKbRnXdyEyKRelp4nJ1kAz32+s27jP1FsebpJCVl0BsvA==} engines: {node: '>=18.0'} - cronstrue@2.52.0: - resolution: {integrity: sha512-NKgHbWkSZXJUcaBHSsyzC8eegD6bBd4O0oCI6XMIJ+y4Bq3v4w7sY3wfWoKPuVlq9pQHRB6od0lmKpIqi8TlKA==} + cronstrue@2.53.0: + resolution: {integrity: sha512-CkAcaI94xL8h6N7cGxgXfR5D7oV2yVtDzB9vMZP8tIgPyEv/oc/7nq9rlk7LMxvc3N+q6LKZmNLCVxJRpyEg8A==} hasBin: true cross-spawn@7.0.6: @@ -3013,8 +3073,8 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} - domutils@3.2.1: - resolution: {integrity: sha512-xWXmuRnN9OMP6ptPd2+H0cCbcYBULa5YDTbMm/2lvkWvNA3O4wcW+GvzooqBuNM8yy6pl3VIAeJTUUWUbfI5Fw==} + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} @@ -3047,8 +3107,8 @@ packages: engines: {node: '>=0.10.0'} hasBin: true - electron-to-chromium@1.5.76: - resolution: {integrity: sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ==} + electron-to-chromium@1.5.88: + resolution: {integrity: sha512-K3C2qf1o+bGzbilTDCTBhTQcMS9KW60yTAaTeeXsfvQuTDDwlokLam/AdqlqcSy9u4UainDgsHV23ksXAOgamw==} email-addresses@5.0.0: resolution: {integrity: sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==} @@ -3125,8 +3185,8 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-object-atoms@1.0.0: - resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} es-set-tostringtag@2.1.0: @@ -3227,8 +3287,8 @@ packages: peerDependencies: eslint: '>=0.8.0' - eslint-plugin-jest@28.10.0: - resolution: {integrity: sha512-hyMWUxkBH99HpXT3p8hc7REbEZK3D+nk8vHXGgpB+XXsi0gO4PxMSP+pjfUzb67GnV9yawV9a53eUmcde1CCZA==} + eslint-plugin-jest@28.11.0: + resolution: {integrity: sha512-QAfipLcNCWLVocVbZW8GimKn5p5iiMcgGbRzz8z/P5q7xw+cNEpYqyzFMtIF/ZgF2HLOyy+dYBut+DoYolvqig==} engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0} peerDependencies: '@typescript-eslint/eslint-plugin': ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -3430,8 +3490,9 @@ packages: flatted@3.3.2: resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} - for-each@0.3.3: - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + for-each@0.3.4: + resolution: {integrity: sha512-kKaIINnFpzW6ffJNDjjyjrk21BkDx38c0xa/klsT8VzLCaMEefv4ZTacrcVR4DmgTeBra++jMDAfS/tS799YDw==} + engines: {node: '>= 0.4'} foreground-child@2.0.0: resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==} @@ -3457,6 +3518,10 @@ packages: resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} engines: {node: '>=14.14'} + fs-extra@11.3.0: + resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} + engines: {node: '>=14.14'} + fs-minipass@2.1.0: resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} engines: {node: '>= 8'} @@ -3543,8 +3608,8 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} - get-tsconfig@4.8.1: - resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} + get-tsconfig@4.10.0: + resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} git-log-parser@1.2.1: resolution: {integrity: sha512-PI+sPDvHXNPl5WNOErAK05s3j0lgwUzMN6o8cyQrDaKfT3qd7TmNJKeXX+SknI5I0QhG5fVPAEwSY4tRGDtYoQ==} @@ -3578,8 +3643,8 @@ packages: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true - glob@11.0.0: - resolution: {integrity: sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==} + glob@11.0.1: + resolution: {integrity: sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==} engines: {node: 20 || >=22} hasBin: true @@ -3615,8 +3680,8 @@ packages: resolution: {integrity: sha512-QUcQZutczESpdo2w9BMG6VpLFoq9ix7ER5HLM1mAdZdri2F3eISkCb8ep84W6YOo0grYWJdyT/8JkYqGjQfSSQ==} engines: {node: '>=18.12.0', yarn: ^1.17.0} - google-auth-library@9.15.0: - resolution: {integrity: sha512-7ccSEJFDFO7exFbO6NRyC+xH8/mZ1GZGG2xxx9iHxZWcjUjJpjWxIMw3cofAKcueZ6DATiukmmprD7yavQHOyQ==} + google-auth-library@9.15.1: + resolution: {integrity: sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==} engines: {node: '>=14'} gopd@1.2.0: @@ -3633,8 +3698,8 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - graph-data-structure@4.3.0: - resolution: {integrity: sha512-rj0SZL3BLXHGZ/K6+Mw0PNrPu+x0uvHukr5dN/10u4Z77ROz5D+D66R+a3X9yZqJKVDcg/fN1FM5NfMX18ErAQ==} + graph-data-structure@4.3.1: + resolution: {integrity: sha512-S+Qp6xtMb4AAWsixMHShDx/X/RYaWQMvs+IksTW6jPyamRzx7xILfOdJ+qPfcbcteZeKa7KDRqeEQv+jHlrBmw==} grapheme-splitter@1.0.4: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} @@ -3788,8 +3853,8 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@6.0.2: - resolution: {integrity: sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==} + ignore@7.0.3: + resolution: {integrity: sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==} engines: {node: '>= 4'} immediate@3.0.6: @@ -3799,10 +3864,6 @@ packages: 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-from-esm@2.0.0: resolution: {integrity: sha512-YVt14UZCgsX1vZQ3gKjkWVdBdHQ6eu3MPU1TBgL1H5orXe2+jWD006WCPPtOuwlQm10NuzOW5WawiF1Q9veW8g==} engines: {node: '>=18.20'} @@ -3887,8 +3948,8 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-async-function@2.1.0: - resolution: {integrity: sha512-GExz9MtyhlZyXYLxzlJRj5WUCE661zhDa1Yna52CN57AJsymh+DvXXjyveSioqSRdxvUrdKdvqB1b5cVKsNpWQ==} + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} engines: {node: '>= 0.4'} is-bigint@1.1.0: @@ -4349,8 +4410,8 @@ packages: jws@4.0.0: resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} - katex@0.16.19: - resolution: {integrity: sha512-3IA6DYVhxhBabjSLTNO9S4+OliA3Qvb8pBQXMfC4WxXJgLwZgnfDl0BmB4z6nBMdznBsZ+CGM8DrGZ5hcguDZg==} + katex@0.16.21: + resolution: {integrity: sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==} hasBin: true keybase-ecurve@1.0.1: @@ -4424,6 +4485,7 @@ packages: lodash.get@4.4.2: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} + deprecated: This package is deprecated. Use the optional chaining (?.) operator instead. lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} @@ -4443,8 +4505,8 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - long@5.2.3: - resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} + long@5.2.4: + resolution: {integrity: sha512-qtzLbJE8hq7VabR3mISmVGtoXP8KGc2Z/AT8OuqlYD7JTR3oqrgwdjnk07wpj1twXxYmgDXgoKVWUG/fReSzHg==} longest-streak@2.0.4: resolution: {integrity: sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==} @@ -4509,13 +4571,13 @@ packages: peerDependencies: markdownlint-cli2: '>=0.0.4' - markdownlint-cli2@0.17.1: - resolution: {integrity: sha512-n1Im9lhKJJE12/u2N0GWBwPqeb0HGdylN8XpSFg9hbj35+QalY9Vi6mxwUQdG6wlSrrIq9ZDQ0Q85AQG9V2WOg==} + markdownlint-cli2@0.17.2: + resolution: {integrity: sha512-XH06ZOi8wCrtOSSj3p8y3yJzwgzYOSa7lglNyS3fP05JPRzRGyjauBb5UvlLUSCGysMmULS1moxdRHHudV+g/Q==} engines: {node: '>=18'} hasBin: true - markdownlint@0.37.3: - resolution: {integrity: sha512-eoQqH0291YCCjd+Pe1PUQ9AmWthlVmS0XWgcionkZ8q34ceZyRI+pYvsWksXJJL8OBkWCPwp1h/pnXxrPFC4oA==} + markdownlint@0.37.4: + resolution: {integrity: sha512-u00joA/syf3VhWh6/ybVFkib5Zpj2e5KB/cfCei8fkSRuums6nyisTWGqjTWIOFoFwuXoTBQQiqlB4qFKp8ncQ==} engines: {node: '>=18'} marked-terminal@7.2.1: @@ -4555,8 +4617,8 @@ packages: mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} - memfs@4.15.2: - resolution: {integrity: sha512-n8/qP8AT6CtY6kxCPYgYVusT5rS6axaT66dD3tYi2lm+l1iMH7YYpmW8H/qL5bfV4YvInCCgUDAWIRvrNS7kbQ==} + memfs@4.17.0: + resolution: {integrity: sha512-4eirfZ7thblFmqFjywlTmuWVSvccHAJbn1r8qQLzmTO11qcqpohOjmY2mFce6x7x7WtskzRqApPD0hv+Oa74jg==} engines: {node: '>= 4.0.0'} memorystream@0.3.1: @@ -4645,8 +4707,8 @@ packages: micromark-util-sanitize-uri@2.0.1: resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} - micromark-util-subtokenize@2.0.3: - resolution: {integrity: sha512-VXJJuNxYWSoYL6AJ6OQECCFGhIU2GGHMw8tahogePBrjkG8aCCas3ibkp7RnVOSTClg2is05/R7maAhF1XyQMg==} + micromark-util-subtokenize@2.0.4: + resolution: {integrity: sha512-N6hXjrin2GTJDe3MVjf5FuXpm12PGm80BrUAeub9XFXca8JZbP+oIwY4LJSVwFUCL1IPm/WwSVUN7goFHmSGGQ==} micromark-util-symbol@2.0.1: resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} @@ -4798,8 +4860,8 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - napi-build-utils@1.0.2: - resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -4825,8 +4887,8 @@ packages: resolution: {integrity: sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ==} engines: {node: '>= 10.13'} - node-abi@3.71.0: - resolution: {integrity: sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw==} + node-abi@3.73.0: + resolution: {integrity: sha512-z8iYzQGBu35ZkTQ9mtR8RqugJZ9RCLn8fv3d7LsgDBzOijGQP3RdKTX4LA7LXw03ZhU5z0l4xfhIMgSES31+cg==} engines: {node: '>=10'} node-emoji@2.2.0: @@ -4847,8 +4909,8 @@ packages: engines: {node: ^16.14.0 || >=18.0.0} hasBin: true - node-html-parser@6.1.13: - resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==} + node-html-parser@7.0.1: + resolution: {integrity: sha512-KGtmPY2kS0thCWGK0VuPyOS+pBKhhe8gXztzA2ilAOhbUbxa9homF1bOyKvhGzMLXUoRds9IOmr/v5lr/lqNmA==} node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -5267,8 +5329,8 @@ packages: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} - prebuild-install@7.1.2: - resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} engines: {node: '>=10'} hasBin: true @@ -5344,8 +5406,8 @@ packages: resolution: {integrity: sha512-L/e3qq/3m/TrYtINo2aBB98oz6w8VHGyFy+arSKwPMZDUNNw2OaQxYnZO6UIZZw2OnRl2qkxGmuSOEfsuHXJdA==} engines: {node: '>=0.10.0'} - qs@6.13.1: - resolution: {integrity: sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==} + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} queue-microtask@1.2.3: @@ -5540,8 +5602,8 @@ packages: sax@1.4.1: resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} - semantic-release@24.2.0: - resolution: {integrity: sha512-fQfn6e/aYToRtVJYKqneFM1Rg3KP2gh3wSWtpYsLlz6uaPKlISrTzvYAFn+mYWo07F0X1Cz5ucU89AVE8X1mbg==} + semantic-release@24.2.1: + resolution: {integrity: sha512-z0/3cutKNkLQ4Oy0HTi3lubnjTsdjjgOqmxdPjeYWe6lhFqUPfwslZxRHv3HDZlN4MhnZitb9SLihDkZNxOXfQ==} engines: {node: '>=20.8.1'} hasBin: true @@ -5713,8 +5775,8 @@ packages: spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - spdx-license-ids@3.0.20: - resolution: {integrity: sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==} + spdx-license-ids@3.0.21: + resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==} split2@1.0.0: resolution: {integrity: sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==} @@ -5850,8 +5912,8 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} - tar-fs@2.1.1: - resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} + tar-fs@2.1.2: + resolution: {integrity: sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==} tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} @@ -5954,11 +6016,11 @@ packages: trough@1.0.5: resolution: {integrity: sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==} - ts-api-utils@1.4.3: - resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} - engines: {node: '>=16'} + ts-api-utils@2.0.0: + resolution: {integrity: sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==} + engines: {node: '>=18.12'} peerDependencies: - typescript: '>=4.2.0' + typescript: '>=4.8.4' ts-essentials@10.0.4: resolution: {integrity: sha512-lwYdz28+S4nicm+jFi6V58LaAIpxzhg9rLdgNC1VsdP/xiFBseGhF1M/shwCk6zMmwahBZdXcl34LVHrEang3A==} @@ -6081,8 +6143,8 @@ packages: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} - type-fest@4.31.0: - resolution: {integrity: sha512-yCxltHW07Nkhv/1F6wWBr8kz+5BGMfP+RbRSYFnegVb0qV/UMT0G0ElBloPVerqn4M2ZV80Ir1FtCcYv1cT6vQ==} + type-fest@4.33.0: + resolution: {integrity: sha512-s6zVrxuyKbbAsSAD5ZPTB77q4YIdRctkTbJ2/Dqlinwz+8ooH2gd+YA7VA6Pa93KML9GockVvoxjZ2vHP+mu8g==} engines: {node: '>=16'} typed-array-buffer@1.0.3: @@ -6108,8 +6170,8 @@ packages: typedarray-to-buffer@3.1.5: resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} - typescript@5.7.2: - resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} engines: {node: '>=14.17'} hasBin: true @@ -6131,8 +6193,8 @@ packages: underscore@1.13.7: resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==} - undici-types@6.19.8: - resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} unicode-emoji-modifier-base@1.0.0: resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} @@ -6195,8 +6257,8 @@ packages: resolution: {integrity: sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==} engines: {node: '>=4'} - update-browserslist-db@1.1.1: - resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} + update-browserslist-db@1.1.2: + resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -6235,6 +6297,10 @@ packages: validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + validate-npm-package-name@5.0.0: + resolution: {integrity: sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + validate-npm-package-name@6.0.0: resolution: {integrity: sha512-d7KLgL1LD3U3fgnvWEY1cQXoO/q6EQ1BSz48Sa149V/5zVTAbgmZIpyI8TRi6U9/JNyeYLlTKsEMPtLC27RFUg==} engines: {node: ^18.17.0 || >=20.5.0} @@ -6422,21 +6488,21 @@ snapshots: '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.714.0 + '@aws-sdk/types': 3.734.0 tslib: 2.8.1 '@aws-crypto/crc32c@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.714.0 + '@aws-sdk/types': 3.734.0 tslib: 2.8.1 '@aws-crypto/sha1-browser@5.2.0': dependencies: '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.714.0 - '@aws-sdk/util-locate-window': 3.693.0 + '@aws-sdk/types': 3.734.0 + '@aws-sdk/util-locate-window': 3.723.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 @@ -6445,15 +6511,15 @@ snapshots: '@aws-crypto/sha256-js': 5.2.0 '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.714.0 - '@aws-sdk/util-locate-window': 3.693.0 + '@aws-sdk/types': 3.734.0 + '@aws-sdk/util-locate-window': 3.723.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 '@aws-crypto/sha256-js@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.714.0 + '@aws-sdk/types': 3.734.0 tslib: 2.8.1 '@aws-crypto/supports-web-crypto@5.2.0': @@ -6462,768 +6528,708 @@ snapshots: '@aws-crypto/util@5.2.0': dependencies: - '@aws-sdk/types': 3.714.0 + '@aws-sdk/types': 3.734.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 - '@aws-sdk/client-codecommit@3.716.0': + '@aws-sdk/client-codecommit@3.738.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.716.0(@aws-sdk/client-sts@3.716.0) - '@aws-sdk/client-sts': 3.716.0 - '@aws-sdk/core': 3.716.0 - '@aws-sdk/credential-provider-node': 3.716.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0))(@aws-sdk/client-sts@3.716.0) - '@aws-sdk/middleware-host-header': 3.714.0 - '@aws-sdk/middleware-logger': 3.714.0 - '@aws-sdk/middleware-recursion-detection': 3.714.0 - '@aws-sdk/middleware-user-agent': 3.716.0 - '@aws-sdk/region-config-resolver': 3.714.0 - '@aws-sdk/types': 3.714.0 - '@aws-sdk/util-endpoints': 3.714.0 - '@aws-sdk/util-user-agent-browser': 3.714.0 - '@aws-sdk/util-user-agent-node': 3.716.0 - '@smithy/config-resolver': 3.0.13 - '@smithy/core': 2.5.7 - '@smithy/fetch-http-handler': 4.1.3 - '@smithy/hash-node': 3.0.11 - '@smithy/invalid-dependency': 3.0.11 - '@smithy/middleware-content-length': 3.0.13 - '@smithy/middleware-endpoint': 3.2.8 - '@smithy/middleware-retry': 3.0.34 - '@smithy/middleware-serde': 3.0.11 - '@smithy/middleware-stack': 3.0.11 - '@smithy/node-config-provider': 3.1.12 - '@smithy/node-http-handler': 3.3.3 - '@smithy/protocol-http': 4.1.8 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/url-parser': 3.0.11 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.34 - '@smithy/util-defaults-mode-node': 3.0.34 - '@smithy/util-endpoints': 2.1.7 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-retry': 3.0.11 - '@smithy/util-utf8': 3.0.0 + '@aws-sdk/core': 3.734.0 + '@aws-sdk/credential-provider-node': 3.738.0 + '@aws-sdk/middleware-host-header': 3.734.0 + '@aws-sdk/middleware-logger': 3.734.0 + '@aws-sdk/middleware-recursion-detection': 3.734.0 + '@aws-sdk/middleware-user-agent': 3.734.0 + '@aws-sdk/region-config-resolver': 3.734.0 + '@aws-sdk/types': 3.734.0 + '@aws-sdk/util-endpoints': 3.734.0 + '@aws-sdk/util-user-agent-browser': 3.734.0 + '@aws-sdk/util-user-agent-node': 3.734.0 + '@smithy/config-resolver': 4.0.1 + '@smithy/core': 3.1.1 + '@smithy/fetch-http-handler': 5.0.1 + '@smithy/hash-node': 4.0.1 + '@smithy/invalid-dependency': 4.0.1 + '@smithy/middleware-content-length': 4.0.1 + '@smithy/middleware-endpoint': 4.0.2 + '@smithy/middleware-retry': 4.0.3 + '@smithy/middleware-serde': 4.0.1 + '@smithy/middleware-stack': 4.0.1 + '@smithy/node-config-provider': 4.0.1 + '@smithy/node-http-handler': 4.0.2 + '@smithy/protocol-http': 5.0.1 + '@smithy/smithy-client': 4.1.2 + '@smithy/types': 4.1.0 + '@smithy/url-parser': 4.0.1 + '@smithy/util-base64': 4.0.0 + '@smithy/util-body-length-browser': 4.0.0 + '@smithy/util-body-length-node': 4.0.0 + '@smithy/util-defaults-mode-browser': 4.0.3 + '@smithy/util-defaults-mode-node': 4.0.3 + '@smithy/util-endpoints': 3.0.1 + '@smithy/util-middleware': 4.0.1 + '@smithy/util-retry': 4.0.1 + '@smithy/util-utf8': 4.0.0 '@types/uuid': 9.0.8 tslib: 2.8.1 uuid: 9.0.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/client-cognito-identity@3.716.0': + '@aws-sdk/client-cognito-identity@3.738.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.716.0(@aws-sdk/client-sts@3.716.0) - '@aws-sdk/client-sts': 3.716.0 - '@aws-sdk/core': 3.716.0 - '@aws-sdk/credential-provider-node': 3.716.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0))(@aws-sdk/client-sts@3.716.0) - '@aws-sdk/middleware-host-header': 3.714.0 - '@aws-sdk/middleware-logger': 3.714.0 - '@aws-sdk/middleware-recursion-detection': 3.714.0 - '@aws-sdk/middleware-user-agent': 3.716.0 - '@aws-sdk/region-config-resolver': 3.714.0 - '@aws-sdk/types': 3.714.0 - '@aws-sdk/util-endpoints': 3.714.0 - '@aws-sdk/util-user-agent-browser': 3.714.0 - '@aws-sdk/util-user-agent-node': 3.716.0 - '@smithy/config-resolver': 3.0.13 - '@smithy/core': 2.5.7 - '@smithy/fetch-http-handler': 4.1.3 - '@smithy/hash-node': 3.0.11 - '@smithy/invalid-dependency': 3.0.11 - '@smithy/middleware-content-length': 3.0.13 - '@smithy/middleware-endpoint': 3.2.8 - '@smithy/middleware-retry': 3.0.34 - '@smithy/middleware-serde': 3.0.11 - '@smithy/middleware-stack': 3.0.11 - '@smithy/node-config-provider': 3.1.12 - '@smithy/node-http-handler': 3.3.3 - '@smithy/protocol-http': 4.1.8 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/url-parser': 3.0.11 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.34 - '@smithy/util-defaults-mode-node': 3.0.34 - '@smithy/util-endpoints': 2.1.7 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-retry': 3.0.11 - '@smithy/util-utf8': 3.0.0 + '@aws-sdk/core': 3.734.0 + '@aws-sdk/credential-provider-node': 3.738.0 + '@aws-sdk/middleware-host-header': 3.734.0 + '@aws-sdk/middleware-logger': 3.734.0 + '@aws-sdk/middleware-recursion-detection': 3.734.0 + '@aws-sdk/middleware-user-agent': 3.734.0 + '@aws-sdk/region-config-resolver': 3.734.0 + '@aws-sdk/types': 3.734.0 + '@aws-sdk/util-endpoints': 3.734.0 + '@aws-sdk/util-user-agent-browser': 3.734.0 + '@aws-sdk/util-user-agent-node': 3.734.0 + '@smithy/config-resolver': 4.0.1 + '@smithy/core': 3.1.1 + '@smithy/fetch-http-handler': 5.0.1 + '@smithy/hash-node': 4.0.1 + '@smithy/invalid-dependency': 4.0.1 + '@smithy/middleware-content-length': 4.0.1 + '@smithy/middleware-endpoint': 4.0.2 + '@smithy/middleware-retry': 4.0.3 + '@smithy/middleware-serde': 4.0.1 + '@smithy/middleware-stack': 4.0.1 + '@smithy/node-config-provider': 4.0.1 + '@smithy/node-http-handler': 4.0.2 + '@smithy/protocol-http': 5.0.1 + '@smithy/smithy-client': 4.1.2 + '@smithy/types': 4.1.0 + '@smithy/url-parser': 4.0.1 + '@smithy/util-base64': 4.0.0 + '@smithy/util-body-length-browser': 4.0.0 + '@smithy/util-body-length-node': 4.0.0 + '@smithy/util-defaults-mode-browser': 4.0.3 + '@smithy/util-defaults-mode-node': 4.0.3 + '@smithy/util-endpoints': 3.0.1 + '@smithy/util-middleware': 4.0.1 + '@smithy/util-retry': 4.0.1 + '@smithy/util-utf8': 4.0.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/client-ec2@3.716.0': + '@aws-sdk/client-ec2@3.738.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.716.0(@aws-sdk/client-sts@3.716.0) - '@aws-sdk/client-sts': 3.716.0 - '@aws-sdk/core': 3.716.0 - '@aws-sdk/credential-provider-node': 3.716.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0))(@aws-sdk/client-sts@3.716.0) - '@aws-sdk/middleware-host-header': 3.714.0 - '@aws-sdk/middleware-logger': 3.714.0 - '@aws-sdk/middleware-recursion-detection': 3.714.0 - '@aws-sdk/middleware-sdk-ec2': 3.716.0 - '@aws-sdk/middleware-user-agent': 3.716.0 - '@aws-sdk/region-config-resolver': 3.714.0 - '@aws-sdk/types': 3.714.0 - '@aws-sdk/util-endpoints': 3.714.0 - '@aws-sdk/util-user-agent-browser': 3.714.0 - '@aws-sdk/util-user-agent-node': 3.716.0 - '@smithy/config-resolver': 3.0.13 - '@smithy/core': 2.5.7 - '@smithy/fetch-http-handler': 4.1.3 - '@smithy/hash-node': 3.0.11 - '@smithy/invalid-dependency': 3.0.11 - '@smithy/middleware-content-length': 3.0.13 - '@smithy/middleware-endpoint': 3.2.8 - '@smithy/middleware-retry': 3.0.34 - '@smithy/middleware-serde': 3.0.11 - '@smithy/middleware-stack': 3.0.11 - '@smithy/node-config-provider': 3.1.12 - '@smithy/node-http-handler': 3.3.3 - '@smithy/protocol-http': 4.1.8 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/url-parser': 3.0.11 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.34 - '@smithy/util-defaults-mode-node': 3.0.34 - '@smithy/util-endpoints': 2.1.7 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-retry': 3.0.11 - '@smithy/util-utf8': 3.0.0 - '@smithy/util-waiter': 3.2.0 + '@aws-sdk/core': 3.734.0 + '@aws-sdk/credential-provider-node': 3.738.0 + '@aws-sdk/middleware-host-header': 3.734.0 + '@aws-sdk/middleware-logger': 3.734.0 + '@aws-sdk/middleware-recursion-detection': 3.734.0 + '@aws-sdk/middleware-sdk-ec2': 3.734.0 + '@aws-sdk/middleware-user-agent': 3.734.0 + '@aws-sdk/region-config-resolver': 3.734.0 + '@aws-sdk/types': 3.734.0 + '@aws-sdk/util-endpoints': 3.734.0 + '@aws-sdk/util-user-agent-browser': 3.734.0 + '@aws-sdk/util-user-agent-node': 3.734.0 + '@smithy/config-resolver': 4.0.1 + '@smithy/core': 3.1.1 + '@smithy/fetch-http-handler': 5.0.1 + '@smithy/hash-node': 4.0.1 + '@smithy/invalid-dependency': 4.0.1 + '@smithy/middleware-content-length': 4.0.1 + '@smithy/middleware-endpoint': 4.0.2 + '@smithy/middleware-retry': 4.0.3 + '@smithy/middleware-serde': 4.0.1 + '@smithy/middleware-stack': 4.0.1 + '@smithy/node-config-provider': 4.0.1 + '@smithy/node-http-handler': 4.0.2 + '@smithy/protocol-http': 5.0.1 + '@smithy/smithy-client': 4.1.2 + '@smithy/types': 4.1.0 + '@smithy/url-parser': 4.0.1 + '@smithy/util-base64': 4.0.0 + '@smithy/util-body-length-browser': 4.0.0 + '@smithy/util-body-length-node': 4.0.0 + '@smithy/util-defaults-mode-browser': 4.0.3 + '@smithy/util-defaults-mode-node': 4.0.3 + '@smithy/util-endpoints': 3.0.1 + '@smithy/util-middleware': 4.0.1 + '@smithy/util-retry': 4.0.1 + '@smithy/util-utf8': 4.0.0 + '@smithy/util-waiter': 4.0.2 '@types/uuid': 9.0.8 tslib: 2.8.1 uuid: 9.0.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/client-ecr@3.720.0': + '@aws-sdk/client-ecr@3.739.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.716.0(@aws-sdk/client-sts@3.716.0) - '@aws-sdk/client-sts': 3.716.0 - '@aws-sdk/core': 3.716.0 - '@aws-sdk/credential-provider-node': 3.716.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0))(@aws-sdk/client-sts@3.716.0) - '@aws-sdk/middleware-host-header': 3.714.0 - '@aws-sdk/middleware-logger': 3.714.0 - '@aws-sdk/middleware-recursion-detection': 3.714.0 - '@aws-sdk/middleware-user-agent': 3.716.0 - '@aws-sdk/region-config-resolver': 3.714.0 - '@aws-sdk/types': 3.714.0 - '@aws-sdk/util-endpoints': 3.714.0 - '@aws-sdk/util-user-agent-browser': 3.714.0 - '@aws-sdk/util-user-agent-node': 3.716.0 - '@smithy/config-resolver': 3.0.13 - '@smithy/core': 2.5.7 - '@smithy/fetch-http-handler': 4.1.3 - '@smithy/hash-node': 3.0.11 - '@smithy/invalid-dependency': 3.0.11 - '@smithy/middleware-content-length': 3.0.13 - '@smithy/middleware-endpoint': 3.2.8 - '@smithy/middleware-retry': 3.0.34 - '@smithy/middleware-serde': 3.0.11 - '@smithy/middleware-stack': 3.0.11 - '@smithy/node-config-provider': 3.1.12 - '@smithy/node-http-handler': 3.3.3 - '@smithy/protocol-http': 4.1.8 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/url-parser': 3.0.11 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.34 - '@smithy/util-defaults-mode-node': 3.0.34 - '@smithy/util-endpoints': 2.1.7 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-retry': 3.0.11 - '@smithy/util-utf8': 3.0.0 - '@smithy/util-waiter': 3.2.0 + '@aws-sdk/core': 3.734.0 + '@aws-sdk/credential-provider-node': 3.738.0 + '@aws-sdk/middleware-host-header': 3.734.0 + '@aws-sdk/middleware-logger': 3.734.0 + '@aws-sdk/middleware-recursion-detection': 3.734.0 + '@aws-sdk/middleware-user-agent': 3.734.0 + '@aws-sdk/region-config-resolver': 3.734.0 + '@aws-sdk/types': 3.734.0 + '@aws-sdk/util-endpoints': 3.734.0 + '@aws-sdk/util-user-agent-browser': 3.734.0 + '@aws-sdk/util-user-agent-node': 3.734.0 + '@smithy/config-resolver': 4.0.1 + '@smithy/core': 3.1.1 + '@smithy/fetch-http-handler': 5.0.1 + '@smithy/hash-node': 4.0.1 + '@smithy/invalid-dependency': 4.0.1 + '@smithy/middleware-content-length': 4.0.1 + '@smithy/middleware-endpoint': 4.0.2 + '@smithy/middleware-retry': 4.0.3 + '@smithy/middleware-serde': 4.0.1 + '@smithy/middleware-stack': 4.0.1 + '@smithy/node-config-provider': 4.0.1 + '@smithy/node-http-handler': 4.0.2 + '@smithy/protocol-http': 5.0.1 + '@smithy/smithy-client': 4.1.2 + '@smithy/types': 4.1.0 + '@smithy/url-parser': 4.0.1 + '@smithy/util-base64': 4.0.0 + '@smithy/util-body-length-browser': 4.0.0 + '@smithy/util-body-length-node': 4.0.0 + '@smithy/util-defaults-mode-browser': 4.0.3 + '@smithy/util-defaults-mode-node': 4.0.3 + '@smithy/util-endpoints': 3.0.1 + '@smithy/util-middleware': 4.0.1 + '@smithy/util-retry': 4.0.1 + '@smithy/util-utf8': 4.0.0 + '@smithy/util-waiter': 4.0.2 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/client-rds@3.719.1': + '@aws-sdk/client-rds@3.740.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.716.0(@aws-sdk/client-sts@3.716.0) - '@aws-sdk/client-sts': 3.716.0 - '@aws-sdk/core': 3.716.0 - '@aws-sdk/credential-provider-node': 3.716.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0))(@aws-sdk/client-sts@3.716.0) - '@aws-sdk/middleware-host-header': 3.714.0 - '@aws-sdk/middleware-logger': 3.714.0 - '@aws-sdk/middleware-recursion-detection': 3.714.0 - '@aws-sdk/middleware-sdk-rds': 3.716.0 - '@aws-sdk/middleware-user-agent': 3.716.0 - '@aws-sdk/region-config-resolver': 3.714.0 - '@aws-sdk/types': 3.714.0 - '@aws-sdk/util-endpoints': 3.714.0 - '@aws-sdk/util-user-agent-browser': 3.714.0 - '@aws-sdk/util-user-agent-node': 3.716.0 - '@smithy/config-resolver': 3.0.13 - '@smithy/core': 2.5.7 - '@smithy/fetch-http-handler': 4.1.3 - '@smithy/hash-node': 3.0.11 - '@smithy/invalid-dependency': 3.0.11 - '@smithy/middleware-content-length': 3.0.13 - '@smithy/middleware-endpoint': 3.2.8 - '@smithy/middleware-retry': 3.0.34 - '@smithy/middleware-serde': 3.0.11 - '@smithy/middleware-stack': 3.0.11 - '@smithy/node-config-provider': 3.1.12 - '@smithy/node-http-handler': 3.3.3 - '@smithy/protocol-http': 4.1.8 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/url-parser': 3.0.11 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.34 - '@smithy/util-defaults-mode-node': 3.0.34 - '@smithy/util-endpoints': 2.1.7 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-retry': 3.0.11 - '@smithy/util-utf8': 3.0.0 - '@smithy/util-waiter': 3.2.0 + '@aws-sdk/core': 3.734.0 + '@aws-sdk/credential-provider-node': 3.738.0 + '@aws-sdk/middleware-host-header': 3.734.0 + '@aws-sdk/middleware-logger': 3.734.0 + '@aws-sdk/middleware-recursion-detection': 3.734.0 + '@aws-sdk/middleware-sdk-rds': 3.734.0 + '@aws-sdk/middleware-user-agent': 3.734.0 + '@aws-sdk/region-config-resolver': 3.734.0 + '@aws-sdk/types': 3.734.0 + '@aws-sdk/util-endpoints': 3.734.0 + '@aws-sdk/util-user-agent-browser': 3.734.0 + '@aws-sdk/util-user-agent-node': 3.734.0 + '@smithy/config-resolver': 4.0.1 + '@smithy/core': 3.1.1 + '@smithy/fetch-http-handler': 5.0.1 + '@smithy/hash-node': 4.0.1 + '@smithy/invalid-dependency': 4.0.1 + '@smithy/middleware-content-length': 4.0.1 + '@smithy/middleware-endpoint': 4.0.2 + '@smithy/middleware-retry': 4.0.3 + '@smithy/middleware-serde': 4.0.1 + '@smithy/middleware-stack': 4.0.1 + '@smithy/node-config-provider': 4.0.1 + '@smithy/node-http-handler': 4.0.2 + '@smithy/protocol-http': 5.0.1 + '@smithy/smithy-client': 4.1.2 + '@smithy/types': 4.1.0 + '@smithy/url-parser': 4.0.1 + '@smithy/util-base64': 4.0.0 + '@smithy/util-body-length-browser': 4.0.0 + '@smithy/util-body-length-node': 4.0.0 + '@smithy/util-defaults-mode-browser': 4.0.3 + '@smithy/util-defaults-mode-node': 4.0.3 + '@smithy/util-endpoints': 3.0.1 + '@smithy/util-middleware': 4.0.1 + '@smithy/util-retry': 4.0.1 + '@smithy/util-utf8': 4.0.0 + '@smithy/util-waiter': 4.0.2 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/client-s3@3.717.0': + '@aws-sdk/client-s3@3.740.0': dependencies: '@aws-crypto/sha1-browser': 5.2.0 '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.716.0(@aws-sdk/client-sts@3.716.0) - '@aws-sdk/client-sts': 3.716.0 - '@aws-sdk/core': 3.716.0 - '@aws-sdk/credential-provider-node': 3.716.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0))(@aws-sdk/client-sts@3.716.0) - '@aws-sdk/middleware-bucket-endpoint': 3.714.0 - '@aws-sdk/middleware-expect-continue': 3.714.0 - '@aws-sdk/middleware-flexible-checksums': 3.717.0 - '@aws-sdk/middleware-host-header': 3.714.0 - '@aws-sdk/middleware-location-constraint': 3.714.0 - '@aws-sdk/middleware-logger': 3.714.0 - '@aws-sdk/middleware-recursion-detection': 3.714.0 - '@aws-sdk/middleware-sdk-s3': 3.716.0 - '@aws-sdk/middleware-ssec': 3.714.0 - '@aws-sdk/middleware-user-agent': 3.716.0 - '@aws-sdk/region-config-resolver': 3.714.0 - '@aws-sdk/signature-v4-multi-region': 3.716.0 - '@aws-sdk/types': 3.714.0 - '@aws-sdk/util-endpoints': 3.714.0 - '@aws-sdk/util-user-agent-browser': 3.714.0 - '@aws-sdk/util-user-agent-node': 3.716.0 - '@aws-sdk/xml-builder': 3.709.0 - '@smithy/config-resolver': 3.0.13 - '@smithy/core': 2.5.7 - '@smithy/eventstream-serde-browser': 3.0.14 - '@smithy/eventstream-serde-config-resolver': 3.0.11 - '@smithy/eventstream-serde-node': 3.0.13 - '@smithy/fetch-http-handler': 4.1.3 - '@smithy/hash-blob-browser': 3.1.10 - '@smithy/hash-node': 3.0.11 - '@smithy/hash-stream-node': 3.1.10 - '@smithy/invalid-dependency': 3.0.11 - '@smithy/md5-js': 3.0.11 - '@smithy/middleware-content-length': 3.0.13 - '@smithy/middleware-endpoint': 3.2.8 - '@smithy/middleware-retry': 3.0.34 - '@smithy/middleware-serde': 3.0.11 - '@smithy/middleware-stack': 3.0.11 - '@smithy/node-config-provider': 3.1.12 - '@smithy/node-http-handler': 3.3.3 - '@smithy/protocol-http': 4.1.8 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/url-parser': 3.0.11 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.34 - '@smithy/util-defaults-mode-node': 3.0.34 - '@smithy/util-endpoints': 2.1.7 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-retry': 3.0.11 - '@smithy/util-stream': 3.3.4 - '@smithy/util-utf8': 3.0.0 - '@smithy/util-waiter': 3.2.0 + '@aws-sdk/core': 3.734.0 + '@aws-sdk/credential-provider-node': 3.738.0 + '@aws-sdk/middleware-bucket-endpoint': 3.734.0 + '@aws-sdk/middleware-expect-continue': 3.734.0 + '@aws-sdk/middleware-flexible-checksums': 3.735.0 + '@aws-sdk/middleware-host-header': 3.734.0 + '@aws-sdk/middleware-location-constraint': 3.734.0 + '@aws-sdk/middleware-logger': 3.734.0 + '@aws-sdk/middleware-recursion-detection': 3.734.0 + '@aws-sdk/middleware-sdk-s3': 3.740.0 + '@aws-sdk/middleware-ssec': 3.734.0 + '@aws-sdk/middleware-user-agent': 3.734.0 + '@aws-sdk/region-config-resolver': 3.734.0 + '@aws-sdk/signature-v4-multi-region': 3.740.0 + '@aws-sdk/types': 3.734.0 + '@aws-sdk/util-endpoints': 3.734.0 + '@aws-sdk/util-user-agent-browser': 3.734.0 + '@aws-sdk/util-user-agent-node': 3.734.0 + '@aws-sdk/xml-builder': 3.734.0 + '@smithy/config-resolver': 4.0.1 + '@smithy/core': 3.1.1 + '@smithy/eventstream-serde-browser': 4.0.1 + '@smithy/eventstream-serde-config-resolver': 4.0.1 + '@smithy/eventstream-serde-node': 4.0.1 + '@smithy/fetch-http-handler': 5.0.1 + '@smithy/hash-blob-browser': 4.0.1 + '@smithy/hash-node': 4.0.1 + '@smithy/hash-stream-node': 4.0.1 + '@smithy/invalid-dependency': 4.0.1 + '@smithy/md5-js': 4.0.1 + '@smithy/middleware-content-length': 4.0.1 + '@smithy/middleware-endpoint': 4.0.2 + '@smithy/middleware-retry': 4.0.3 + '@smithy/middleware-serde': 4.0.1 + '@smithy/middleware-stack': 4.0.1 + '@smithy/node-config-provider': 4.0.1 + '@smithy/node-http-handler': 4.0.2 + '@smithy/protocol-http': 5.0.1 + '@smithy/smithy-client': 4.1.2 + '@smithy/types': 4.1.0 + '@smithy/url-parser': 4.0.1 + '@smithy/util-base64': 4.0.0 + '@smithy/util-body-length-browser': 4.0.0 + '@smithy/util-body-length-node': 4.0.0 + '@smithy/util-defaults-mode-browser': 4.0.3 + '@smithy/util-defaults-mode-node': 4.0.3 + '@smithy/util-endpoints': 3.0.1 + '@smithy/util-middleware': 4.0.1 + '@smithy/util-retry': 4.0.1 + '@smithy/util-stream': 4.0.2 + '@smithy/util-utf8': 4.0.0 + '@smithy/util-waiter': 4.0.2 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0)': + '@aws-sdk/client-sso@3.734.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sts': 3.716.0 - '@aws-sdk/core': 3.716.0 - '@aws-sdk/credential-provider-node': 3.716.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0))(@aws-sdk/client-sts@3.716.0) - '@aws-sdk/middleware-host-header': 3.714.0 - '@aws-sdk/middleware-logger': 3.714.0 - '@aws-sdk/middleware-recursion-detection': 3.714.0 - '@aws-sdk/middleware-user-agent': 3.716.0 - '@aws-sdk/region-config-resolver': 3.714.0 - '@aws-sdk/types': 3.714.0 - '@aws-sdk/util-endpoints': 3.714.0 - '@aws-sdk/util-user-agent-browser': 3.714.0 - '@aws-sdk/util-user-agent-node': 3.716.0 - '@smithy/config-resolver': 3.0.13 - '@smithy/core': 2.5.7 - '@smithy/fetch-http-handler': 4.1.3 - '@smithy/hash-node': 3.0.11 - '@smithy/invalid-dependency': 3.0.11 - '@smithy/middleware-content-length': 3.0.13 - '@smithy/middleware-endpoint': 3.2.8 - '@smithy/middleware-retry': 3.0.34 - '@smithy/middleware-serde': 3.0.11 - '@smithy/middleware-stack': 3.0.11 - '@smithy/node-config-provider': 3.1.12 - '@smithy/node-http-handler': 3.3.3 - '@smithy/protocol-http': 4.1.8 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/url-parser': 3.0.11 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.34 - '@smithy/util-defaults-mode-node': 3.0.34 - '@smithy/util-endpoints': 2.1.7 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-retry': 3.0.11 - '@smithy/util-utf8': 3.0.0 + '@aws-sdk/core': 3.734.0 + '@aws-sdk/middleware-host-header': 3.734.0 + '@aws-sdk/middleware-logger': 3.734.0 + '@aws-sdk/middleware-recursion-detection': 3.734.0 + '@aws-sdk/middleware-user-agent': 3.734.0 + '@aws-sdk/region-config-resolver': 3.734.0 + '@aws-sdk/types': 3.734.0 + '@aws-sdk/util-endpoints': 3.734.0 + '@aws-sdk/util-user-agent-browser': 3.734.0 + '@aws-sdk/util-user-agent-node': 3.734.0 + '@smithy/config-resolver': 4.0.1 + '@smithy/core': 3.1.1 + '@smithy/fetch-http-handler': 5.0.1 + '@smithy/hash-node': 4.0.1 + '@smithy/invalid-dependency': 4.0.1 + '@smithy/middleware-content-length': 4.0.1 + '@smithy/middleware-endpoint': 4.0.2 + '@smithy/middleware-retry': 4.0.3 + '@smithy/middleware-serde': 4.0.1 + '@smithy/middleware-stack': 4.0.1 + '@smithy/node-config-provider': 4.0.1 + '@smithy/node-http-handler': 4.0.2 + '@smithy/protocol-http': 5.0.1 + '@smithy/smithy-client': 4.1.2 + '@smithy/types': 4.1.0 + '@smithy/url-parser': 4.0.1 + '@smithy/util-base64': 4.0.0 + '@smithy/util-body-length-browser': 4.0.0 + '@smithy/util-body-length-node': 4.0.0 + '@smithy/util-defaults-mode-browser': 4.0.3 + '@smithy/util-defaults-mode-node': 4.0.3 + '@smithy/util-endpoints': 3.0.1 + '@smithy/util-middleware': 4.0.1 + '@smithy/util-retry': 4.0.1 + '@smithy/util-utf8': 4.0.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso@3.716.0': - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.716.0 - '@aws-sdk/middleware-host-header': 3.714.0 - '@aws-sdk/middleware-logger': 3.714.0 - '@aws-sdk/middleware-recursion-detection': 3.714.0 - '@aws-sdk/middleware-user-agent': 3.716.0 - '@aws-sdk/region-config-resolver': 3.714.0 - '@aws-sdk/types': 3.714.0 - '@aws-sdk/util-endpoints': 3.714.0 - '@aws-sdk/util-user-agent-browser': 3.714.0 - '@aws-sdk/util-user-agent-node': 3.716.0 - '@smithy/config-resolver': 3.0.13 - '@smithy/core': 2.5.7 - '@smithy/fetch-http-handler': 4.1.3 - '@smithy/hash-node': 3.0.11 - '@smithy/invalid-dependency': 3.0.11 - '@smithy/middleware-content-length': 3.0.13 - '@smithy/middleware-endpoint': 3.2.8 - '@smithy/middleware-retry': 3.0.34 - '@smithy/middleware-serde': 3.0.11 - '@smithy/middleware-stack': 3.0.11 - '@smithy/node-config-provider': 3.1.12 - '@smithy/node-http-handler': 3.3.3 - '@smithy/protocol-http': 4.1.8 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/url-parser': 3.0.11 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.34 - '@smithy/util-defaults-mode-node': 3.0.34 - '@smithy/util-endpoints': 2.1.7 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-retry': 3.0.11 - '@smithy/util-utf8': 3.0.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/client-sts@3.716.0': - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.716.0(@aws-sdk/client-sts@3.716.0) - '@aws-sdk/core': 3.716.0 - '@aws-sdk/credential-provider-node': 3.716.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0))(@aws-sdk/client-sts@3.716.0) - '@aws-sdk/middleware-host-header': 3.714.0 - '@aws-sdk/middleware-logger': 3.714.0 - '@aws-sdk/middleware-recursion-detection': 3.714.0 - '@aws-sdk/middleware-user-agent': 3.716.0 - '@aws-sdk/region-config-resolver': 3.714.0 - '@aws-sdk/types': 3.714.0 - '@aws-sdk/util-endpoints': 3.714.0 - '@aws-sdk/util-user-agent-browser': 3.714.0 - '@aws-sdk/util-user-agent-node': 3.716.0 - '@smithy/config-resolver': 3.0.13 - '@smithy/core': 2.5.7 - '@smithy/fetch-http-handler': 4.1.3 - '@smithy/hash-node': 3.0.11 - '@smithy/invalid-dependency': 3.0.11 - '@smithy/middleware-content-length': 3.0.13 - '@smithy/middleware-endpoint': 3.2.8 - '@smithy/middleware-retry': 3.0.34 - '@smithy/middleware-serde': 3.0.11 - '@smithy/middleware-stack': 3.0.11 - '@smithy/node-config-provider': 3.1.12 - '@smithy/node-http-handler': 3.3.3 - '@smithy/protocol-http': 4.1.8 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/url-parser': 3.0.11 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.34 - '@smithy/util-defaults-mode-node': 3.0.34 - '@smithy/util-endpoints': 2.1.7 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-retry': 3.0.11 - '@smithy/util-utf8': 3.0.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/core@3.716.0': - dependencies: - '@aws-sdk/types': 3.714.0 - '@smithy/core': 2.5.7 - '@smithy/node-config-provider': 3.1.12 - '@smithy/property-provider': 3.1.11 - '@smithy/protocol-http': 4.1.8 - '@smithy/signature-v4': 4.2.4 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/util-middleware': 3.0.11 + '@aws-sdk/core@3.734.0': + dependencies: + '@aws-sdk/types': 3.734.0 + '@smithy/core': 3.1.1 + '@smithy/node-config-provider': 4.0.1 + '@smithy/property-provider': 4.0.1 + '@smithy/protocol-http': 5.0.1 + '@smithy/signature-v4': 5.0.1 + '@smithy/smithy-client': 4.1.2 + '@smithy/types': 4.1.0 + '@smithy/util-middleware': 4.0.1 fast-xml-parser: 4.4.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-cognito-identity@3.716.0': + '@aws-sdk/credential-provider-cognito-identity@3.738.0': dependencies: - '@aws-sdk/client-cognito-identity': 3.716.0 - '@aws-sdk/types': 3.714.0 - '@smithy/property-provider': 3.1.11 - '@smithy/types': 3.7.2 + '@aws-sdk/client-cognito-identity': 3.738.0 + '@aws-sdk/types': 3.734.0 + '@smithy/property-provider': 4.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-env@3.716.0': + '@aws-sdk/credential-provider-env@3.734.0': dependencies: - '@aws-sdk/core': 3.716.0 - '@aws-sdk/types': 3.714.0 - '@smithy/property-provider': 3.1.11 - '@smithy/types': 3.7.2 + '@aws-sdk/core': 3.734.0 + '@aws-sdk/types': 3.734.0 + '@smithy/property-provider': 4.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-http@3.716.0': - dependencies: - '@aws-sdk/core': 3.716.0 - '@aws-sdk/types': 3.714.0 - '@smithy/fetch-http-handler': 4.1.3 - '@smithy/node-http-handler': 3.3.3 - '@smithy/property-provider': 3.1.11 - '@smithy/protocol-http': 4.1.8 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/util-stream': 3.3.4 + '@aws-sdk/credential-provider-http@3.734.0': + dependencies: + '@aws-sdk/core': 3.734.0 + '@aws-sdk/types': 3.734.0 + '@smithy/fetch-http-handler': 5.0.1 + '@smithy/node-http-handler': 4.0.2 + '@smithy/property-provider': 4.0.1 + '@smithy/protocol-http': 5.0.1 + '@smithy/smithy-client': 4.1.2 + '@smithy/types': 4.1.0 + '@smithy/util-stream': 4.0.2 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.716.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0))(@aws-sdk/client-sts@3.716.0)': - dependencies: - '@aws-sdk/client-sts': 3.716.0 - '@aws-sdk/core': 3.716.0 - '@aws-sdk/credential-provider-env': 3.716.0 - '@aws-sdk/credential-provider-http': 3.716.0 - '@aws-sdk/credential-provider-process': 3.716.0 - '@aws-sdk/credential-provider-sso': 3.716.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0)) - '@aws-sdk/credential-provider-web-identity': 3.716.0(@aws-sdk/client-sts@3.716.0) - '@aws-sdk/types': 3.714.0 - '@smithy/credential-provider-imds': 3.2.8 - '@smithy/property-provider': 3.1.11 - '@smithy/shared-ini-file-loader': 3.1.12 - '@smithy/types': 3.7.2 + '@aws-sdk/credential-provider-ini@3.734.0': + dependencies: + '@aws-sdk/core': 3.734.0 + '@aws-sdk/credential-provider-env': 3.734.0 + '@aws-sdk/credential-provider-http': 3.734.0 + '@aws-sdk/credential-provider-process': 3.734.0 + '@aws-sdk/credential-provider-sso': 3.734.0 + '@aws-sdk/credential-provider-web-identity': 3.734.0 + '@aws-sdk/nested-clients': 3.734.0 + '@aws-sdk/types': 3.734.0 + '@smithy/credential-provider-imds': 4.0.1 + '@smithy/property-provider': 4.0.1 + '@smithy/shared-ini-file-loader': 4.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - aws-crt - '@aws-sdk/credential-provider-node@3.716.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0))(@aws-sdk/client-sts@3.716.0)': - dependencies: - '@aws-sdk/credential-provider-env': 3.716.0 - '@aws-sdk/credential-provider-http': 3.716.0 - '@aws-sdk/credential-provider-ini': 3.716.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0))(@aws-sdk/client-sts@3.716.0) - '@aws-sdk/credential-provider-process': 3.716.0 - '@aws-sdk/credential-provider-sso': 3.716.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0)) - '@aws-sdk/credential-provider-web-identity': 3.716.0(@aws-sdk/client-sts@3.716.0) - '@aws-sdk/types': 3.714.0 - '@smithy/credential-provider-imds': 3.2.8 - '@smithy/property-provider': 3.1.11 - '@smithy/shared-ini-file-loader': 3.1.12 - '@smithy/types': 3.7.2 + '@aws-sdk/credential-provider-node@3.738.0': + dependencies: + '@aws-sdk/credential-provider-env': 3.734.0 + '@aws-sdk/credential-provider-http': 3.734.0 + '@aws-sdk/credential-provider-ini': 3.734.0 + '@aws-sdk/credential-provider-process': 3.734.0 + '@aws-sdk/credential-provider-sso': 3.734.0 + '@aws-sdk/credential-provider-web-identity': 3.734.0 + '@aws-sdk/types': 3.734.0 + '@smithy/credential-provider-imds': 4.0.1 + '@smithy/property-provider': 4.0.1 + '@smithy/shared-ini-file-loader': 4.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - - '@aws-sdk/client-sts' - aws-crt - '@aws-sdk/credential-provider-process@3.716.0': + '@aws-sdk/credential-provider-process@3.734.0': dependencies: - '@aws-sdk/core': 3.716.0 - '@aws-sdk/types': 3.714.0 - '@smithy/property-provider': 3.1.11 - '@smithy/shared-ini-file-loader': 3.1.12 - '@smithy/types': 3.7.2 + '@aws-sdk/core': 3.734.0 + '@aws-sdk/types': 3.734.0 + '@smithy/property-provider': 4.0.1 + '@smithy/shared-ini-file-loader': 4.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.716.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0))': + '@aws-sdk/credential-provider-sso@3.734.0': dependencies: - '@aws-sdk/client-sso': 3.716.0 - '@aws-sdk/core': 3.716.0 - '@aws-sdk/token-providers': 3.714.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0)) - '@aws-sdk/types': 3.714.0 - '@smithy/property-provider': 3.1.11 - '@smithy/shared-ini-file-loader': 3.1.12 - '@smithy/types': 3.7.2 + '@aws-sdk/client-sso': 3.734.0 + '@aws-sdk/core': 3.734.0 + '@aws-sdk/token-providers': 3.734.0 + '@aws-sdk/types': 3.734.0 + '@smithy/property-provider': 4.0.1 + '@smithy/shared-ini-file-loader': 4.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - aws-crt - '@aws-sdk/credential-provider-web-identity@3.716.0(@aws-sdk/client-sts@3.716.0)': + '@aws-sdk/credential-provider-web-identity@3.734.0': dependencies: - '@aws-sdk/client-sts': 3.716.0 - '@aws-sdk/core': 3.716.0 - '@aws-sdk/types': 3.714.0 - '@smithy/property-provider': 3.1.11 - '@smithy/types': 3.7.2 + '@aws-sdk/core': 3.734.0 + '@aws-sdk/nested-clients': 3.734.0 + '@aws-sdk/types': 3.734.0 + '@smithy/property-provider': 4.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt - '@aws-sdk/credential-providers@3.716.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0))': - dependencies: - '@aws-sdk/client-cognito-identity': 3.716.0 - '@aws-sdk/client-sso': 3.716.0 - '@aws-sdk/client-sts': 3.716.0 - '@aws-sdk/core': 3.716.0 - '@aws-sdk/credential-provider-cognito-identity': 3.716.0 - '@aws-sdk/credential-provider-env': 3.716.0 - '@aws-sdk/credential-provider-http': 3.716.0 - '@aws-sdk/credential-provider-ini': 3.716.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0))(@aws-sdk/client-sts@3.716.0) - '@aws-sdk/credential-provider-node': 3.716.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0))(@aws-sdk/client-sts@3.716.0) - '@aws-sdk/credential-provider-process': 3.716.0 - '@aws-sdk/credential-provider-sso': 3.716.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0)) - '@aws-sdk/credential-provider-web-identity': 3.716.0(@aws-sdk/client-sts@3.716.0) - '@aws-sdk/types': 3.714.0 - '@smithy/credential-provider-imds': 3.2.8 - '@smithy/property-provider': 3.1.11 - '@smithy/types': 3.7.2 + '@aws-sdk/credential-providers@3.738.0': + dependencies: + '@aws-sdk/client-cognito-identity': 3.738.0 + '@aws-sdk/core': 3.734.0 + '@aws-sdk/credential-provider-cognito-identity': 3.738.0 + '@aws-sdk/credential-provider-env': 3.734.0 + '@aws-sdk/credential-provider-http': 3.734.0 + '@aws-sdk/credential-provider-ini': 3.734.0 + '@aws-sdk/credential-provider-node': 3.738.0 + '@aws-sdk/credential-provider-process': 3.734.0 + '@aws-sdk/credential-provider-sso': 3.734.0 + '@aws-sdk/credential-provider-web-identity': 3.734.0 + '@aws-sdk/nested-clients': 3.734.0 + '@aws-sdk/types': 3.734.0 + '@smithy/core': 3.1.1 + '@smithy/credential-provider-imds': 4.0.1 + '@smithy/property-provider': 4.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - aws-crt - '@aws-sdk/middleware-bucket-endpoint@3.714.0': + '@aws-sdk/middleware-bucket-endpoint@3.734.0': dependencies: - '@aws-sdk/types': 3.714.0 - '@aws-sdk/util-arn-parser': 3.693.0 - '@smithy/node-config-provider': 3.1.12 - '@smithy/protocol-http': 4.1.8 - '@smithy/types': 3.7.2 - '@smithy/util-config-provider': 3.0.0 + '@aws-sdk/types': 3.734.0 + '@aws-sdk/util-arn-parser': 3.723.0 + '@smithy/node-config-provider': 4.0.1 + '@smithy/protocol-http': 5.0.1 + '@smithy/types': 4.1.0 + '@smithy/util-config-provider': 4.0.0 tslib: 2.8.1 - '@aws-sdk/middleware-expect-continue@3.714.0': + '@aws-sdk/middleware-expect-continue@3.734.0': dependencies: - '@aws-sdk/types': 3.714.0 - '@smithy/protocol-http': 4.1.8 - '@smithy/types': 3.7.2 + '@aws-sdk/types': 3.734.0 + '@smithy/protocol-http': 5.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@aws-sdk/middleware-flexible-checksums@3.717.0': + '@aws-sdk/middleware-flexible-checksums@3.735.0': dependencies: '@aws-crypto/crc32': 5.2.0 '@aws-crypto/crc32c': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/core': 3.716.0 - '@aws-sdk/types': 3.714.0 - '@smithy/is-array-buffer': 3.0.0 - '@smithy/node-config-provider': 3.1.12 - '@smithy/protocol-http': 4.1.8 - '@smithy/types': 3.7.2 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-stream': 3.3.4 - '@smithy/util-utf8': 3.0.0 + '@aws-sdk/core': 3.734.0 + '@aws-sdk/types': 3.734.0 + '@smithy/is-array-buffer': 4.0.0 + '@smithy/node-config-provider': 4.0.1 + '@smithy/protocol-http': 5.0.1 + '@smithy/types': 4.1.0 + '@smithy/util-middleware': 4.0.1 + '@smithy/util-stream': 4.0.2 + '@smithy/util-utf8': 4.0.0 tslib: 2.8.1 - '@aws-sdk/middleware-host-header@3.714.0': + '@aws-sdk/middleware-host-header@3.734.0': dependencies: - '@aws-sdk/types': 3.714.0 - '@smithy/protocol-http': 4.1.8 - '@smithy/types': 3.7.2 + '@aws-sdk/types': 3.734.0 + '@smithy/protocol-http': 5.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@aws-sdk/middleware-location-constraint@3.714.0': + '@aws-sdk/middleware-location-constraint@3.734.0': dependencies: - '@aws-sdk/types': 3.714.0 - '@smithy/types': 3.7.2 + '@aws-sdk/types': 3.734.0 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@aws-sdk/middleware-logger@3.714.0': + '@aws-sdk/middleware-logger@3.734.0': dependencies: - '@aws-sdk/types': 3.714.0 - '@smithy/types': 3.7.2 + '@aws-sdk/types': 3.734.0 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@aws-sdk/middleware-recursion-detection@3.714.0': + '@aws-sdk/middleware-recursion-detection@3.734.0': dependencies: - '@aws-sdk/types': 3.714.0 - '@smithy/protocol-http': 4.1.8 - '@smithy/types': 3.7.2 + '@aws-sdk/types': 3.734.0 + '@smithy/protocol-http': 5.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@aws-sdk/middleware-sdk-ec2@3.716.0': + '@aws-sdk/middleware-sdk-ec2@3.734.0': dependencies: - '@aws-sdk/types': 3.714.0 - '@aws-sdk/util-format-url': 3.714.0 - '@smithy/middleware-endpoint': 3.2.8 - '@smithy/protocol-http': 4.1.8 - '@smithy/signature-v4': 4.2.4 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 + '@aws-sdk/types': 3.734.0 + '@aws-sdk/util-format-url': 3.734.0 + '@smithy/middleware-endpoint': 4.0.2 + '@smithy/protocol-http': 5.0.1 + '@smithy/signature-v4': 5.0.1 + '@smithy/smithy-client': 4.1.2 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@aws-sdk/middleware-sdk-rds@3.716.0': + '@aws-sdk/middleware-sdk-rds@3.734.0': dependencies: - '@aws-sdk/types': 3.714.0 - '@aws-sdk/util-format-url': 3.714.0 - '@smithy/middleware-endpoint': 3.2.8 - '@smithy/protocol-http': 4.1.8 - '@smithy/signature-v4': 4.2.4 - '@smithy/types': 3.7.2 + '@aws-sdk/types': 3.734.0 + '@aws-sdk/util-format-url': 3.734.0 + '@smithy/middleware-endpoint': 4.0.2 + '@smithy/protocol-http': 5.0.1 + '@smithy/signature-v4': 5.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@aws-sdk/middleware-sdk-s3@3.716.0': - dependencies: - '@aws-sdk/core': 3.716.0 - '@aws-sdk/types': 3.714.0 - '@aws-sdk/util-arn-parser': 3.693.0 - '@smithy/core': 2.5.7 - '@smithy/node-config-provider': 3.1.12 - '@smithy/protocol-http': 4.1.8 - '@smithy/signature-v4': 4.2.4 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/util-config-provider': 3.0.0 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-stream': 3.3.4 - '@smithy/util-utf8': 3.0.0 + '@aws-sdk/middleware-sdk-s3@3.740.0': + dependencies: + '@aws-sdk/core': 3.734.0 + '@aws-sdk/types': 3.734.0 + '@aws-sdk/util-arn-parser': 3.723.0 + '@smithy/core': 3.1.1 + '@smithy/node-config-provider': 4.0.1 + '@smithy/protocol-http': 5.0.1 + '@smithy/signature-v4': 5.0.1 + '@smithy/smithy-client': 4.1.2 + '@smithy/types': 4.1.0 + '@smithy/util-config-provider': 4.0.0 + '@smithy/util-middleware': 4.0.1 + '@smithy/util-stream': 4.0.2 + '@smithy/util-utf8': 4.0.0 tslib: 2.8.1 - '@aws-sdk/middleware-ssec@3.714.0': + '@aws-sdk/middleware-ssec@3.734.0': dependencies: - '@aws-sdk/types': 3.714.0 - '@smithy/types': 3.7.2 + '@aws-sdk/types': 3.734.0 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@aws-sdk/middleware-user-agent@3.716.0': + '@aws-sdk/middleware-user-agent@3.734.0': dependencies: - '@aws-sdk/core': 3.716.0 - '@aws-sdk/types': 3.714.0 - '@aws-sdk/util-endpoints': 3.714.0 - '@smithy/core': 2.5.7 - '@smithy/protocol-http': 4.1.8 - '@smithy/types': 3.7.2 + '@aws-sdk/core': 3.734.0 + '@aws-sdk/types': 3.734.0 + '@aws-sdk/util-endpoints': 3.734.0 + '@smithy/core': 3.1.1 + '@smithy/protocol-http': 5.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@aws-sdk/region-config-resolver@3.714.0': + '@aws-sdk/nested-clients@3.734.0': dependencies: - '@aws-sdk/types': 3.714.0 - '@smithy/node-config-provider': 3.1.12 - '@smithy/types': 3.7.2 - '@smithy/util-config-provider': 3.0.0 - '@smithy/util-middleware': 3.0.11 + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.734.0 + '@aws-sdk/middleware-host-header': 3.734.0 + '@aws-sdk/middleware-logger': 3.734.0 + '@aws-sdk/middleware-recursion-detection': 3.734.0 + '@aws-sdk/middleware-user-agent': 3.734.0 + '@aws-sdk/region-config-resolver': 3.734.0 + '@aws-sdk/types': 3.734.0 + '@aws-sdk/util-endpoints': 3.734.0 + '@aws-sdk/util-user-agent-browser': 3.734.0 + '@aws-sdk/util-user-agent-node': 3.734.0 + '@smithy/config-resolver': 4.0.1 + '@smithy/core': 3.1.1 + '@smithy/fetch-http-handler': 5.0.1 + '@smithy/hash-node': 4.0.1 + '@smithy/invalid-dependency': 4.0.1 + '@smithy/middleware-content-length': 4.0.1 + '@smithy/middleware-endpoint': 4.0.2 + '@smithy/middleware-retry': 4.0.3 + '@smithy/middleware-serde': 4.0.1 + '@smithy/middleware-stack': 4.0.1 + '@smithy/node-config-provider': 4.0.1 + '@smithy/node-http-handler': 4.0.2 + '@smithy/protocol-http': 5.0.1 + '@smithy/smithy-client': 4.1.2 + '@smithy/types': 4.1.0 + '@smithy/url-parser': 4.0.1 + '@smithy/util-base64': 4.0.0 + '@smithy/util-body-length-browser': 4.0.0 + '@smithy/util-body-length-node': 4.0.0 + '@smithy/util-defaults-mode-browser': 4.0.3 + '@smithy/util-defaults-mode-node': 4.0.3 + '@smithy/util-endpoints': 3.0.1 + '@smithy/util-middleware': 4.0.1 + '@smithy/util-retry': 4.0.1 + '@smithy/util-utf8': 4.0.0 tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt - '@aws-sdk/signature-v4-multi-region@3.716.0': + '@aws-sdk/region-config-resolver@3.734.0': dependencies: - '@aws-sdk/middleware-sdk-s3': 3.716.0 - '@aws-sdk/types': 3.714.0 - '@smithy/protocol-http': 4.1.8 - '@smithy/signature-v4': 4.2.4 - '@smithy/types': 3.7.2 + '@aws-sdk/types': 3.734.0 + '@smithy/node-config-provider': 4.0.1 + '@smithy/types': 4.1.0 + '@smithy/util-config-provider': 4.0.0 + '@smithy/util-middleware': 4.0.1 tslib: 2.8.1 - '@aws-sdk/token-providers@3.714.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0))': + '@aws-sdk/signature-v4-multi-region@3.740.0': dependencies: - '@aws-sdk/client-sso-oidc': 3.716.0(@aws-sdk/client-sts@3.716.0) - '@aws-sdk/types': 3.714.0 - '@smithy/property-provider': 3.1.11 - '@smithy/shared-ini-file-loader': 3.1.12 - '@smithy/types': 3.7.2 + '@aws-sdk/middleware-sdk-s3': 3.740.0 + '@aws-sdk/types': 3.734.0 + '@smithy/protocol-http': 5.0.1 + '@smithy/signature-v4': 5.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@aws-sdk/types@3.714.0': + '@aws-sdk/token-providers@3.734.0': + dependencies: + '@aws-sdk/nested-clients': 3.734.0 + '@aws-sdk/types': 3.734.0 + '@smithy/property-provider': 4.0.1 + '@smithy/shared-ini-file-loader': 4.0.1 + '@smithy/types': 4.1.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/types@3.734.0': dependencies: - '@smithy/types': 3.7.2 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@aws-sdk/util-arn-parser@3.693.0': + '@aws-sdk/util-arn-parser@3.723.0': dependencies: tslib: 2.8.1 - '@aws-sdk/util-endpoints@3.714.0': + '@aws-sdk/util-endpoints@3.734.0': dependencies: - '@aws-sdk/types': 3.714.0 - '@smithy/types': 3.7.2 - '@smithy/util-endpoints': 2.1.7 + '@aws-sdk/types': 3.734.0 + '@smithy/types': 4.1.0 + '@smithy/util-endpoints': 3.0.1 tslib: 2.8.1 - '@aws-sdk/util-format-url@3.714.0': + '@aws-sdk/util-format-url@3.734.0': dependencies: - '@aws-sdk/types': 3.714.0 - '@smithy/querystring-builder': 3.0.11 - '@smithy/types': 3.7.2 + '@aws-sdk/types': 3.734.0 + '@smithy/querystring-builder': 4.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@aws-sdk/util-locate-window@3.693.0': + '@aws-sdk/util-locate-window@3.723.0': dependencies: tslib: 2.8.1 - '@aws-sdk/util-user-agent-browser@3.714.0': + '@aws-sdk/util-user-agent-browser@3.734.0': dependencies: - '@aws-sdk/types': 3.714.0 - '@smithy/types': 3.7.2 + '@aws-sdk/types': 3.734.0 + '@smithy/types': 4.1.0 bowser: 2.11.0 tslib: 2.8.1 - '@aws-sdk/util-user-agent-node@3.716.0': + '@aws-sdk/util-user-agent-node@3.734.0': dependencies: - '@aws-sdk/middleware-user-agent': 3.716.0 - '@aws-sdk/types': 3.714.0 - '@smithy/node-config-provider': 3.1.12 - '@smithy/types': 3.7.2 + '@aws-sdk/middleware-user-agent': 3.734.0 + '@aws-sdk/types': 3.734.0 + '@smithy/node-config-provider': 4.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@aws-sdk/xml-builder@3.709.0': + '@aws-sdk/xml-builder@3.734.0': dependencies: - '@smithy/types': 3.7.2 + '@smithy/types': 4.1.0 tslib: 2.8.1 '@babel/code-frame@7.26.2': @@ -7232,20 +7238,20 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.26.3': {} + '@babel/compat-data@7.26.5': {} - '@babel/core@7.26.0': + '@babel/core@7.26.7': dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.3 - '@babel/helper-compilation-targets': 7.25.9 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) - '@babel/helpers': 7.26.0 - '@babel/parser': 7.26.3 + '@babel/generator': 7.26.5 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.7) + '@babel/helpers': 7.26.7 + '@babel/parser': 7.26.7 '@babel/template': 7.25.9 - '@babel/traverse': 7.26.4 - '@babel/types': 7.26.3 + '@babel/traverse': 7.26.7 + '@babel/types': 7.26.7 convert-source-map: 2.0.0 debug: 4.4.0 gensync: 1.0.0-beta.2 @@ -7254,39 +7260,39 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.26.3': + '@babel/generator@7.26.5': dependencies: - '@babel/parser': 7.26.3 - '@babel/types': 7.26.3 + '@babel/parser': 7.26.7 + '@babel/types': 7.26.7 '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.1.0 - '@babel/helper-compilation-targets@7.25.9': + '@babel/helper-compilation-targets@7.26.5': dependencies: - '@babel/compat-data': 7.26.3 + '@babel/compat-data': 7.26.5 '@babel/helper-validator-option': 7.25.9 - browserslist: 4.24.3 + browserslist: 4.24.4 lru-cache: 5.1.1 semver: 6.3.1 '@babel/helper-module-imports@7.25.9': dependencies: - '@babel/traverse': 7.26.4 - '@babel/types': 7.26.3 + '@babel/traverse': 7.26.7 + '@babel/types': 7.26.7 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-module-imports': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.26.4 + '@babel/traverse': 7.26.7 transitivePeerDependencies: - supports-color - '@babel/helper-plugin-utils@7.25.9': {} + '@babel/helper-plugin-utils@7.26.5': {} '@babel/helper-string-parser@7.25.9': {} @@ -7294,124 +7300,124 @@ snapshots: '@babel/helper-validator-option@7.25.9': {} - '@babel/helpers@7.26.0': + '@babel/helpers@7.26.7': dependencies: '@babel/template': 7.25.9 - '@babel/types': 7.26.3 + '@babel/types': 7.26.7 - '@babel/parser@7.26.3': + '@babel/parser@7.26.7': dependencies: - '@babel/types': 7.26.3 + '@babel/types': 7.26.7 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.26.0)': + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.7 + '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.7 + '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.0)': + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.7 + '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.26.0)': + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.7 + '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.0)': + '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.7 + '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.0)': + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.7 + '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.7 + '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.7 + '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.26.0)': + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.7 + '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.7 + '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.0)': + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.7 + '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.7 + '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.7 + '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.7 + '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.26.0)': + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.7 + '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.0)': + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.7 + '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.7 + '@babel/helper-plugin-utils': 7.26.5 - '@babel/runtime-corejs3@7.26.0': + '@babel/runtime-corejs3@7.26.7': dependencies: - core-js-pure: 3.39.0 + core-js-pure: 3.40.0 regenerator-runtime: 0.14.1 '@babel/template@7.25.9': dependencies: '@babel/code-frame': 7.26.2 - '@babel/parser': 7.26.3 - '@babel/types': 7.26.3 + '@babel/parser': 7.26.7 + '@babel/types': 7.26.7 - '@babel/traverse@7.26.4': + '@babel/traverse@7.26.7': dependencies: '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.3 - '@babel/parser': 7.26.3 + '@babel/generator': 7.26.5 + '@babel/parser': 7.26.7 '@babel/template': 7.25.9 - '@babel/types': 7.26.3 + '@babel/types': 7.26.7 debug: 4.4.0 globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/types@7.26.3': + '@babel/types@7.26.7': dependencies: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 @@ -7420,7 +7426,7 @@ snapshots: '@breejs/later@4.2.0': {} - '@cdktf/hcl2json@0.20.10': + '@cdktf/hcl2json@0.20.11': dependencies: fs-extra: 11.2.0 @@ -7496,27 +7502,27 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 20.17.11 + '@types/node': 22.10.9 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2))': + '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3))': 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': 20.17.11 + '@types/node': 22.10.9 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@20.17.11)(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2)) + jest-config: 29.7.0(@types/node@22.10.9)(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -7541,7 +7547,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.11 + '@types/node': 22.10.9 jest-mock: 29.7.0 '@jest/expect-utils@29.4.1': @@ -7563,7 +7569,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.17.11 + '@types/node': 22.10.9 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -7585,7 +7591,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 20.17.11 + '@types/node': 22.10.9 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -7632,7 +7638,7 @@ snapshots: '@jest/transform@29.7.0': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 babel-plugin-istanbul: 6.1.1 @@ -7655,7 +7661,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.17.11 + '@types/node': 22.10.9 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -7751,7 +7757,7 @@ snapshots: '@octokit/graphql': 7.1.0 '@octokit/request': 8.4.0 '@octokit/request-error': 5.1.0 - '@octokit/types': 13.6.2 + '@octokit/types': 13.7.0 before-after-hook: 2.2.3 universal-user-agent: 6.0.1 @@ -7759,45 +7765,45 @@ snapshots: dependencies: '@octokit/auth-token': 5.1.1 '@octokit/graphql': 8.1.2 - '@octokit/request': 9.1.4 + '@octokit/request': 9.2.0 '@octokit/request-error': 6.1.6 - '@octokit/types': 13.6.2 + '@octokit/types': 13.7.0 before-after-hook: 3.0.2 universal-user-agent: 7.0.2 '@octokit/endpoint@10.1.2': dependencies: - '@octokit/types': 13.6.2 + '@octokit/types': 13.7.0 universal-user-agent: 7.0.2 '@octokit/endpoint@9.0.5': dependencies: - '@octokit/types': 13.6.2 + '@octokit/types': 13.7.0 universal-user-agent: 6.0.1 '@octokit/graphql@7.1.0': dependencies: '@octokit/request': 8.4.0 - '@octokit/types': 13.6.2 + '@octokit/types': 13.7.0 universal-user-agent: 6.0.1 '@octokit/graphql@8.1.2': dependencies: - '@octokit/request': 9.1.4 - '@octokit/types': 13.6.2 + '@octokit/request': 9.2.0 + '@octokit/types': 13.7.0 universal-user-agent: 7.0.2 - '@octokit/openapi-types@22.2.0': {} + '@octokit/openapi-types@23.0.1': {} '@octokit/plugin-paginate-rest@11.3.1(@octokit/core@5.2.0)': dependencies: '@octokit/core': 5.2.0 - '@octokit/types': 13.6.2 + '@octokit/types': 13.7.0 - '@octokit/plugin-paginate-rest@11.3.6(@octokit/core@6.1.3)': + '@octokit/plugin-paginate-rest@11.4.0(@octokit/core@6.1.3)': dependencies: '@octokit/core': 6.1.3 - '@octokit/types': 13.6.2 + '@octokit/types': 13.7.0 '@octokit/plugin-request-log@4.0.1(@octokit/core@5.2.0)': dependencies: @@ -7806,43 +7812,43 @@ snapshots: '@octokit/plugin-rest-endpoint-methods@13.2.2(@octokit/core@5.2.0)': dependencies: '@octokit/core': 5.2.0 - '@octokit/types': 13.6.2 + '@octokit/types': 13.7.0 - '@octokit/plugin-retry@7.1.2(@octokit/core@6.1.3)': + '@octokit/plugin-retry@7.1.3(@octokit/core@6.1.3)': dependencies: '@octokit/core': 6.1.3 '@octokit/request-error': 6.1.6 - '@octokit/types': 13.6.2 + '@octokit/types': 13.7.0 bottleneck: 2.19.5 - '@octokit/plugin-throttling@9.3.2(@octokit/core@6.1.3)': + '@octokit/plugin-throttling@9.4.0(@octokit/core@6.1.3)': dependencies: '@octokit/core': 6.1.3 - '@octokit/types': 13.6.2 + '@octokit/types': 13.7.0 bottleneck: 2.19.5 '@octokit/request-error@5.1.0': dependencies: - '@octokit/types': 13.6.2 + '@octokit/types': 13.7.0 deprecation: 2.3.1 once: 1.4.0 '@octokit/request-error@6.1.6': dependencies: - '@octokit/types': 13.6.2 + '@octokit/types': 13.7.0 '@octokit/request@8.4.0': dependencies: '@octokit/endpoint': 9.0.5 '@octokit/request-error': 5.1.0 - '@octokit/types': 13.6.2 + '@octokit/types': 13.7.0 universal-user-agent: 6.0.1 - '@octokit/request@9.1.4': + '@octokit/request@9.2.0': dependencies: '@octokit/endpoint': 10.1.2 '@octokit/request-error': 6.1.6 - '@octokit/types': 13.6.2 + '@octokit/types': 13.7.0 fast-content-type-parse: 2.0.1 universal-user-agent: 7.0.2 @@ -7853,64 +7859,64 @@ snapshots: '@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@13.6.2': + '@octokit/types@13.7.0': dependencies: - '@octokit/openapi-types': 22.2.0 + '@octokit/openapi-types': 23.0.1 '@one-ini/wasm@0.1.1': {} - '@openpgp/web-stream-tools@0.1.3(typescript@5.7.2)': + '@openpgp/web-stream-tools@0.1.3(typescript@5.7.3)': optionalDependencies: - typescript: 5.7.2 + typescript: 5.7.3 - '@opentelemetry/api-logs@0.57.0': + '@opentelemetry/api-logs@0.57.1': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/api@1.9.0': {} - '@opentelemetry/context-async-hooks@1.30.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core@1.30.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.28.0 - '@opentelemetry/exporter-trace-otlp-http@0.57.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-trace-otlp-http@0.57.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.57.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.57.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.30.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.57.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.57.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-bunyan@0.45.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs': 0.57.0 - '@opentelemetry/instrumentation': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api-logs': 0.57.1 + '@opentelemetry/instrumentation': 0.57.1(@opentelemetry/api@1.9.0) '@types/bunyan': 1.8.9 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-http@0.57.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-http@0.57.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.57.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.28.0 forwarded-parse: 2.1.2 semver: 7.6.3 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation@0.57.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation@0.57.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs': 0.57.0 + '@opentelemetry/api-logs': 0.57.1 '@types/shimmer': 1.2.0 import-in-the-middle: 1.12.0 require-in-the-middle: 7.4.0 @@ -7919,67 +7925,67 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/otlp-exporter-base@0.57.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/otlp-exporter-base@0.57.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.57.1(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer@0.57.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/otlp-transformer@0.57.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs': 0.57.0 - '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.30.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-logs': 0.57.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics': 1.30.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api-logs': 0.57.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.57.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) protobufjs: 7.4.0 - '@opentelemetry/propagator-b3@1.30.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/propagator-b3@1.30.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/propagator-jaeger@1.30.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/propagator-jaeger@1.30.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/resources@1.30.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.28.0 - '@opentelemetry/sdk-logs@0.57.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-logs@0.57.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs': 0.57.0 - '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api-logs': 0.57.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics@1.30.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-metrics@1.30.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base@1.30.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.28.0 - '@opentelemetry/sdk-trace-node@1.30.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-trace-node@1.30.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/context-async-hooks': 1.30.0(@opentelemetry/api@1.9.0) - '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) - '@opentelemetry/propagator-b3': 1.30.0(@opentelemetry/api@1.9.0) - '@opentelemetry/propagator-jaeger': 1.30.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-b3': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-jaeger': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) semver: 7.6.3 '@opentelemetry/semantic-conventions@1.28.0': {} @@ -7987,10 +7993,25 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@pnpm/catalogs.protocol-parser@1000.0.0': {} + + '@pnpm/catalogs.resolver@1000.0.1': + dependencies: + '@pnpm/catalogs.protocol-parser': 1000.0.0 + '@pnpm/error': 1000.0.1 + + '@pnpm/catalogs.types@1000.0.0': {} + '@pnpm/config.env-replace@1.1.0': {} + '@pnpm/constants@1001.0.0': {} + '@pnpm/constants@6.1.0': {} + '@pnpm/error@1000.0.1': + dependencies: + '@pnpm/constants': 1001.0.0 + '@pnpm/error@4.0.0': dependencies: '@pnpm/constants': 6.1.0 @@ -8009,6 +8030,17 @@ snapshots: '@pnpm/network.ca-file': 1.0.2 config-chain: 1.1.13 + '@pnpm/parse-overrides@1000.0.1': + dependencies: + '@pnpm/catalogs.resolver': 1000.0.1 + '@pnpm/catalogs.types': 1000.0.0 + '@pnpm/error': 1000.0.1 + '@pnpm/parse-wanted-dependency': 1000.0.0 + + '@pnpm/parse-wanted-dependency@1000.0.0': + dependencies: + validate-npm-package-name: 5.0.0 + '@pnpm/read-project-manifest@4.1.1': dependencies: '@gwhitney/detect-indent': 7.0.1 @@ -8066,7 +8098,7 @@ snapshots: '@qnighy/marshal@0.1.3': dependencies: - '@babel/runtime-corejs3': 7.26.0 + '@babel/runtime-corejs3': 7.26.7 '@redis/bloom@1.2.0(@redis/client@1.6.0)': dependencies: @@ -8096,7 +8128,7 @@ snapshots: '@renovatebot/detect-tools@1.1.0': dependencies: - fs-extra: 11.2.0 + fs-extra: 11.3.0 toml-eslint-parser: 0.10.0 upath: 2.0.1 zod: 3.24.1 @@ -8122,12 +8154,12 @@ snapshots: dependencies: '@seald-io/nedb': 4.0.4 - '@renovatebot/osv-offline@1.5.11(encoding@0.1.13)': + '@renovatebot/osv-offline@1.5.12(encoding@0.1.13)': dependencies: '@octokit/rest': 20.1.1 '@renovatebot/osv-offline-db': 1.7.0 adm-zip: 0.5.16 - fs-extra: 11.2.0 + fs-extra: 11.3.0 got: 11.8.6 luxon: 3.5.0 node-fetch: 2.7.0(encoding@0.1.13) @@ -8150,7 +8182,7 @@ snapshots: '@sec-ant/readable-stream@0.4.1': {} - '@semantic-release/commit-analyzer@13.0.1(semantic-release@24.2.0(typescript@5.7.2))': + '@semantic-release/commit-analyzer@13.0.1(semantic-release@24.2.1(typescript@5.7.3))': dependencies: conventional-changelog-angular: 8.0.0 conventional-changelog-writer: 8.0.0 @@ -8160,7 +8192,7 @@ snapshots: import-from-esm: 2.0.0 lodash-es: 4.17.21 micromatch: 4.0.8 - semantic-release: 24.2.0(typescript@5.7.2) + semantic-release: 24.2.1(typescript@5.7.3) transitivePeerDependencies: - supports-color @@ -8168,7 +8200,7 @@ snapshots: '@semantic-release/error@4.0.0': {} - '@semantic-release/exec@6.0.3(semantic-release@24.2.0(typescript@5.7.2))': + '@semantic-release/exec@6.0.3(semantic-release@24.2.1(typescript@5.7.3))': dependencies: '@semantic-release/error': 3.0.0 aggregate-error: 3.1.0 @@ -8176,16 +8208,16 @@ snapshots: execa: 5.1.1 lodash: 4.17.21 parse-json: 5.2.0 - semantic-release: 24.2.0(typescript@5.7.2) + semantic-release: 24.2.1(typescript@5.7.3) transitivePeerDependencies: - supports-color - '@semantic-release/github@11.0.1(semantic-release@24.2.0(typescript@5.7.2))': + '@semantic-release/github@11.0.1(semantic-release@24.2.1(typescript@5.7.3))': dependencies: '@octokit/core': 6.1.3 - '@octokit/plugin-paginate-rest': 11.3.6(@octokit/core@6.1.3) - '@octokit/plugin-retry': 7.1.2(@octokit/core@6.1.3) - '@octokit/plugin-throttling': 9.3.2(@octokit/core@6.1.3) + '@octokit/plugin-paginate-rest': 11.4.0(@octokit/core@6.1.3) + '@octokit/plugin-retry': 7.1.3(@octokit/core@6.1.3) + '@octokit/plugin-throttling': 9.4.0(@octokit/core@6.1.3) '@semantic-release/error': 4.0.0 aggregate-error: 5.0.0 debug: 4.4.0 @@ -8197,17 +8229,17 @@ snapshots: lodash-es: 4.17.21 mime: 4.0.6 p-filter: 4.1.0 - semantic-release: 24.2.0(typescript@5.7.2) + semantic-release: 24.2.1(typescript@5.7.3) url-join: 5.0.0 transitivePeerDependencies: - supports-color - '@semantic-release/npm@12.0.1(semantic-release@24.2.0(typescript@5.7.2))': + '@semantic-release/npm@12.0.1(semantic-release@24.2.1(typescript@5.7.3))': dependencies: '@semantic-release/error': 4.0.0 aggregate-error: 5.0.0 execa: 9.5.2 - fs-extra: 11.2.0 + fs-extra: 11.3.0 lodash-es: 4.17.21 nerf-dart: 1.0.0 normalize-url: 8.0.1 @@ -8215,11 +8247,11 @@ snapshots: rc: 1.2.8 read-pkg: 9.0.1 registry-auth-token: 5.0.3 - semantic-release: 24.2.0(typescript@5.7.2) + semantic-release: 24.2.1(typescript@5.7.3) semver: 7.6.3 tempy: 3.1.0 - '@semantic-release/release-notes-generator@14.0.3(semantic-release@24.2.0(typescript@5.7.2))': + '@semantic-release/release-notes-generator@14.0.3(semantic-release@24.2.1(typescript@5.7.3))': dependencies: conventional-changelog-angular: 8.0.0 conventional-changelog-writer: 8.0.0 @@ -8231,7 +8263,7 @@ snapshots: into-stream: 7.0.0 lodash-es: 4.17.21 read-package-up: 11.0.0 - semantic-release: 24.2.0(typescript@5.7.2) + semantic-release: 24.2.1(typescript@5.7.3) transitivePeerDependencies: - supports-color @@ -8267,250 +8299,250 @@ snapshots: '@sinonjs/text-encoding@0.7.3': {} - '@smithy/abort-controller@3.1.9': + '@smithy/abort-controller@4.0.1': dependencies: - '@smithy/types': 3.7.2 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@smithy/chunked-blob-reader-native@3.0.1': + '@smithy/chunked-blob-reader-native@4.0.0': dependencies: - '@smithy/util-base64': 3.0.0 + '@smithy/util-base64': 4.0.0 tslib: 2.8.1 - '@smithy/chunked-blob-reader@4.0.0': + '@smithy/chunked-blob-reader@5.0.0': dependencies: tslib: 2.8.1 - '@smithy/config-resolver@3.0.13': + '@smithy/config-resolver@4.0.1': dependencies: - '@smithy/node-config-provider': 3.1.12 - '@smithy/types': 3.7.2 - '@smithy/util-config-provider': 3.0.0 - '@smithy/util-middleware': 3.0.11 + '@smithy/node-config-provider': 4.0.1 + '@smithy/types': 4.1.0 + '@smithy/util-config-provider': 4.0.0 + '@smithy/util-middleware': 4.0.1 tslib: 2.8.1 - '@smithy/core@2.5.7': + '@smithy/core@3.1.1': dependencies: - '@smithy/middleware-serde': 3.0.11 - '@smithy/protocol-http': 4.1.8 - '@smithy/types': 3.7.2 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-stream': 3.3.4 - '@smithy/util-utf8': 3.0.0 + '@smithy/middleware-serde': 4.0.1 + '@smithy/protocol-http': 5.0.1 + '@smithy/types': 4.1.0 + '@smithy/util-body-length-browser': 4.0.0 + '@smithy/util-middleware': 4.0.1 + '@smithy/util-stream': 4.0.2 + '@smithy/util-utf8': 4.0.0 tslib: 2.8.1 - '@smithy/credential-provider-imds@3.2.8': + '@smithy/credential-provider-imds@4.0.1': dependencies: - '@smithy/node-config-provider': 3.1.12 - '@smithy/property-provider': 3.1.11 - '@smithy/types': 3.7.2 - '@smithy/url-parser': 3.0.11 + '@smithy/node-config-provider': 4.0.1 + '@smithy/property-provider': 4.0.1 + '@smithy/types': 4.1.0 + '@smithy/url-parser': 4.0.1 tslib: 2.8.1 - '@smithy/eventstream-codec@3.1.10': + '@smithy/eventstream-codec@4.0.1': dependencies: '@aws-crypto/crc32': 5.2.0 - '@smithy/types': 3.7.2 - '@smithy/util-hex-encoding': 3.0.0 + '@smithy/types': 4.1.0 + '@smithy/util-hex-encoding': 4.0.0 tslib: 2.8.1 - '@smithy/eventstream-serde-browser@3.0.14': + '@smithy/eventstream-serde-browser@4.0.1': dependencies: - '@smithy/eventstream-serde-universal': 3.0.13 - '@smithy/types': 3.7.2 + '@smithy/eventstream-serde-universal': 4.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@smithy/eventstream-serde-config-resolver@3.0.11': + '@smithy/eventstream-serde-config-resolver@4.0.1': dependencies: - '@smithy/types': 3.7.2 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@smithy/eventstream-serde-node@3.0.13': + '@smithy/eventstream-serde-node@4.0.1': dependencies: - '@smithy/eventstream-serde-universal': 3.0.13 - '@smithy/types': 3.7.2 + '@smithy/eventstream-serde-universal': 4.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@smithy/eventstream-serde-universal@3.0.13': + '@smithy/eventstream-serde-universal@4.0.1': dependencies: - '@smithy/eventstream-codec': 3.1.10 - '@smithy/types': 3.7.2 + '@smithy/eventstream-codec': 4.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@smithy/fetch-http-handler@4.1.3': + '@smithy/fetch-http-handler@5.0.1': dependencies: - '@smithy/protocol-http': 4.1.8 - '@smithy/querystring-builder': 3.0.11 - '@smithy/types': 3.7.2 - '@smithy/util-base64': 3.0.0 + '@smithy/protocol-http': 5.0.1 + '@smithy/querystring-builder': 4.0.1 + '@smithy/types': 4.1.0 + '@smithy/util-base64': 4.0.0 tslib: 2.8.1 - '@smithy/hash-blob-browser@3.1.10': + '@smithy/hash-blob-browser@4.0.1': dependencies: - '@smithy/chunked-blob-reader': 4.0.0 - '@smithy/chunked-blob-reader-native': 3.0.1 - '@smithy/types': 3.7.2 + '@smithy/chunked-blob-reader': 5.0.0 + '@smithy/chunked-blob-reader-native': 4.0.0 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@smithy/hash-node@3.0.11': + '@smithy/hash-node@4.0.1': dependencies: - '@smithy/types': 3.7.2 - '@smithy/util-buffer-from': 3.0.0 - '@smithy/util-utf8': 3.0.0 + '@smithy/types': 4.1.0 + '@smithy/util-buffer-from': 4.0.0 + '@smithy/util-utf8': 4.0.0 tslib: 2.8.1 - '@smithy/hash-stream-node@3.1.10': + '@smithy/hash-stream-node@4.0.1': dependencies: - '@smithy/types': 3.7.2 - '@smithy/util-utf8': 3.0.0 + '@smithy/types': 4.1.0 + '@smithy/util-utf8': 4.0.0 tslib: 2.8.1 - '@smithy/invalid-dependency@3.0.11': + '@smithy/invalid-dependency@4.0.1': dependencies: - '@smithy/types': 3.7.2 + '@smithy/types': 4.1.0 tslib: 2.8.1 '@smithy/is-array-buffer@2.2.0': dependencies: tslib: 2.8.1 - '@smithy/is-array-buffer@3.0.0': + '@smithy/is-array-buffer@4.0.0': dependencies: tslib: 2.8.1 - '@smithy/md5-js@3.0.11': + '@smithy/md5-js@4.0.1': dependencies: - '@smithy/types': 3.7.2 - '@smithy/util-utf8': 3.0.0 + '@smithy/types': 4.1.0 + '@smithy/util-utf8': 4.0.0 tslib: 2.8.1 - '@smithy/middleware-content-length@3.0.13': + '@smithy/middleware-content-length@4.0.1': dependencies: - '@smithy/protocol-http': 4.1.8 - '@smithy/types': 3.7.2 + '@smithy/protocol-http': 5.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@smithy/middleware-endpoint@3.2.8': + '@smithy/middleware-endpoint@4.0.2': dependencies: - '@smithy/core': 2.5.7 - '@smithy/middleware-serde': 3.0.11 - '@smithy/node-config-provider': 3.1.12 - '@smithy/shared-ini-file-loader': 3.1.12 - '@smithy/types': 3.7.2 - '@smithy/url-parser': 3.0.11 - '@smithy/util-middleware': 3.0.11 + '@smithy/core': 3.1.1 + '@smithy/middleware-serde': 4.0.1 + '@smithy/node-config-provider': 4.0.1 + '@smithy/shared-ini-file-loader': 4.0.1 + '@smithy/types': 4.1.0 + '@smithy/url-parser': 4.0.1 + '@smithy/util-middleware': 4.0.1 tslib: 2.8.1 - '@smithy/middleware-retry@3.0.34': + '@smithy/middleware-retry@4.0.3': dependencies: - '@smithy/node-config-provider': 3.1.12 - '@smithy/protocol-http': 4.1.8 - '@smithy/service-error-classification': 3.0.11 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-retry': 3.0.11 + '@smithy/node-config-provider': 4.0.1 + '@smithy/protocol-http': 5.0.1 + '@smithy/service-error-classification': 4.0.1 + '@smithy/smithy-client': 4.1.2 + '@smithy/types': 4.1.0 + '@smithy/util-middleware': 4.0.1 + '@smithy/util-retry': 4.0.1 tslib: 2.8.1 uuid: 9.0.1 - '@smithy/middleware-serde@3.0.11': + '@smithy/middleware-serde@4.0.1': dependencies: - '@smithy/types': 3.7.2 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@smithy/middleware-stack@3.0.11': + '@smithy/middleware-stack@4.0.1': dependencies: - '@smithy/types': 3.7.2 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@smithy/node-config-provider@3.1.12': + '@smithy/node-config-provider@4.0.1': dependencies: - '@smithy/property-provider': 3.1.11 - '@smithy/shared-ini-file-loader': 3.1.12 - '@smithy/types': 3.7.2 + '@smithy/property-provider': 4.0.1 + '@smithy/shared-ini-file-loader': 4.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@smithy/node-http-handler@3.3.3': + '@smithy/node-http-handler@4.0.2': dependencies: - '@smithy/abort-controller': 3.1.9 - '@smithy/protocol-http': 4.1.8 - '@smithy/querystring-builder': 3.0.11 - '@smithy/types': 3.7.2 + '@smithy/abort-controller': 4.0.1 + '@smithy/protocol-http': 5.0.1 + '@smithy/querystring-builder': 4.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@smithy/property-provider@3.1.11': + '@smithy/property-provider@4.0.1': dependencies: - '@smithy/types': 3.7.2 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@smithy/protocol-http@4.1.8': + '@smithy/protocol-http@5.0.1': dependencies: - '@smithy/types': 3.7.2 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@smithy/querystring-builder@3.0.11': + '@smithy/querystring-builder@4.0.1': dependencies: - '@smithy/types': 3.7.2 - '@smithy/util-uri-escape': 3.0.0 + '@smithy/types': 4.1.0 + '@smithy/util-uri-escape': 4.0.0 tslib: 2.8.1 - '@smithy/querystring-parser@3.0.11': + '@smithy/querystring-parser@4.0.1': dependencies: - '@smithy/types': 3.7.2 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@smithy/service-error-classification@3.0.11': + '@smithy/service-error-classification@4.0.1': dependencies: - '@smithy/types': 3.7.2 + '@smithy/types': 4.1.0 - '@smithy/shared-ini-file-loader@3.1.12': + '@smithy/shared-ini-file-loader@4.0.1': dependencies: - '@smithy/types': 3.7.2 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@smithy/signature-v4@4.2.4': + '@smithy/signature-v4@5.0.1': dependencies: - '@smithy/is-array-buffer': 3.0.0 - '@smithy/protocol-http': 4.1.8 - '@smithy/types': 3.7.2 - '@smithy/util-hex-encoding': 3.0.0 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-uri-escape': 3.0.0 - '@smithy/util-utf8': 3.0.0 + '@smithy/is-array-buffer': 4.0.0 + '@smithy/protocol-http': 5.0.1 + '@smithy/types': 4.1.0 + '@smithy/util-hex-encoding': 4.0.0 + '@smithy/util-middleware': 4.0.1 + '@smithy/util-uri-escape': 4.0.0 + '@smithy/util-utf8': 4.0.0 tslib: 2.8.1 - '@smithy/smithy-client@3.7.0': + '@smithy/smithy-client@4.1.2': dependencies: - '@smithy/core': 2.5.7 - '@smithy/middleware-endpoint': 3.2.8 - '@smithy/middleware-stack': 3.0.11 - '@smithy/protocol-http': 4.1.8 - '@smithy/types': 3.7.2 - '@smithy/util-stream': 3.3.4 + '@smithy/core': 3.1.1 + '@smithy/middleware-endpoint': 4.0.2 + '@smithy/middleware-stack': 4.0.1 + '@smithy/protocol-http': 5.0.1 + '@smithy/types': 4.1.0 + '@smithy/util-stream': 4.0.2 tslib: 2.8.1 - '@smithy/types@3.7.2': + '@smithy/types@4.1.0': dependencies: tslib: 2.8.1 - '@smithy/url-parser@3.0.11': + '@smithy/url-parser@4.0.1': dependencies: - '@smithy/querystring-parser': 3.0.11 - '@smithy/types': 3.7.2 + '@smithy/querystring-parser': 4.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@smithy/util-base64@3.0.0': + '@smithy/util-base64@4.0.0': dependencies: - '@smithy/util-buffer-from': 3.0.0 - '@smithy/util-utf8': 3.0.0 + '@smithy/util-buffer-from': 4.0.0 + '@smithy/util-utf8': 4.0.0 tslib: 2.8.1 - '@smithy/util-body-length-browser@3.0.0': + '@smithy/util-body-length-browser@4.0.0': dependencies: tslib: 2.8.1 - '@smithy/util-body-length-node@3.0.0': + '@smithy/util-body-length-node@4.0.0': dependencies: tslib: 2.8.1 @@ -8519,66 +8551,66 @@ snapshots: '@smithy/is-array-buffer': 2.2.0 tslib: 2.8.1 - '@smithy/util-buffer-from@3.0.0': + '@smithy/util-buffer-from@4.0.0': dependencies: - '@smithy/is-array-buffer': 3.0.0 + '@smithy/is-array-buffer': 4.0.0 tslib: 2.8.1 - '@smithy/util-config-provider@3.0.0': + '@smithy/util-config-provider@4.0.0': dependencies: tslib: 2.8.1 - '@smithy/util-defaults-mode-browser@3.0.34': + '@smithy/util-defaults-mode-browser@4.0.3': dependencies: - '@smithy/property-provider': 3.1.11 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 + '@smithy/property-provider': 4.0.1 + '@smithy/smithy-client': 4.1.2 + '@smithy/types': 4.1.0 bowser: 2.11.0 tslib: 2.8.1 - '@smithy/util-defaults-mode-node@3.0.34': + '@smithy/util-defaults-mode-node@4.0.3': dependencies: - '@smithy/config-resolver': 3.0.13 - '@smithy/credential-provider-imds': 3.2.8 - '@smithy/node-config-provider': 3.1.12 - '@smithy/property-provider': 3.1.11 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 + '@smithy/config-resolver': 4.0.1 + '@smithy/credential-provider-imds': 4.0.1 + '@smithy/node-config-provider': 4.0.1 + '@smithy/property-provider': 4.0.1 + '@smithy/smithy-client': 4.1.2 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@smithy/util-endpoints@2.1.7': + '@smithy/util-endpoints@3.0.1': dependencies: - '@smithy/node-config-provider': 3.1.12 - '@smithy/types': 3.7.2 + '@smithy/node-config-provider': 4.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@smithy/util-hex-encoding@3.0.0': + '@smithy/util-hex-encoding@4.0.0': dependencies: tslib: 2.8.1 - '@smithy/util-middleware@3.0.11': + '@smithy/util-middleware@4.0.1': dependencies: - '@smithy/types': 3.7.2 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@smithy/util-retry@3.0.11': + '@smithy/util-retry@4.0.1': dependencies: - '@smithy/service-error-classification': 3.0.11 - '@smithy/types': 3.7.2 + '@smithy/service-error-classification': 4.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@smithy/util-stream@3.3.4': + '@smithy/util-stream@4.0.2': dependencies: - '@smithy/fetch-http-handler': 4.1.3 - '@smithy/node-http-handler': 3.3.3 - '@smithy/types': 3.7.2 - '@smithy/util-base64': 3.0.0 - '@smithy/util-buffer-from': 3.0.0 - '@smithy/util-hex-encoding': 3.0.0 - '@smithy/util-utf8': 3.0.0 + '@smithy/fetch-http-handler': 5.0.1 + '@smithy/node-http-handler': 4.0.2 + '@smithy/types': 4.1.0 + '@smithy/util-base64': 4.0.0 + '@smithy/util-buffer-from': 4.0.0 + '@smithy/util-hex-encoding': 4.0.0 + '@smithy/util-utf8': 4.0.0 tslib: 2.8.1 - '@smithy/util-uri-escape@3.0.0': + '@smithy/util-uri-escape@4.0.0': dependencies: tslib: 2.8.1 @@ -8587,62 +8619,62 @@ snapshots: '@smithy/util-buffer-from': 2.2.0 tslib: 2.8.1 - '@smithy/util-utf8@3.0.0': + '@smithy/util-utf8@4.0.0': dependencies: - '@smithy/util-buffer-from': 3.0.0 + '@smithy/util-buffer-from': 4.0.0 tslib: 2.8.1 - '@smithy/util-waiter@3.2.0': + '@smithy/util-waiter@4.0.2': dependencies: - '@smithy/abort-controller': 3.1.9 - '@smithy/types': 3.7.2 + '@smithy/abort-controller': 4.0.1 + '@smithy/types': 4.1.0 tslib: 2.8.1 - '@swc/core-darwin-arm64@1.10.4': + '@swc/core-darwin-arm64@1.10.9': optional: true - '@swc/core-darwin-x64@1.10.4': + '@swc/core-darwin-x64@1.10.9': optional: true - '@swc/core-linux-arm-gnueabihf@1.10.4': + '@swc/core-linux-arm-gnueabihf@1.10.9': optional: true - '@swc/core-linux-arm64-gnu@1.10.4': + '@swc/core-linux-arm64-gnu@1.10.9': optional: true - '@swc/core-linux-arm64-musl@1.10.4': + '@swc/core-linux-arm64-musl@1.10.9': optional: true - '@swc/core-linux-x64-gnu@1.10.4': + '@swc/core-linux-x64-gnu@1.10.9': optional: true - '@swc/core-linux-x64-musl@1.10.4': + '@swc/core-linux-x64-musl@1.10.9': optional: true - '@swc/core-win32-arm64-msvc@1.10.4': + '@swc/core-win32-arm64-msvc@1.10.9': optional: true - '@swc/core-win32-ia32-msvc@1.10.4': + '@swc/core-win32-ia32-msvc@1.10.9': optional: true - '@swc/core-win32-x64-msvc@1.10.4': + '@swc/core-win32-x64-msvc@1.10.9': optional: true - '@swc/core@1.10.4': + '@swc/core@1.10.9': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.17 optionalDependencies: - '@swc/core-darwin-arm64': 1.10.4 - '@swc/core-darwin-x64': 1.10.4 - '@swc/core-linux-arm-gnueabihf': 1.10.4 - '@swc/core-linux-arm64-gnu': 1.10.4 - '@swc/core-linux-arm64-musl': 1.10.4 - '@swc/core-linux-x64-gnu': 1.10.4 - '@swc/core-linux-x64-musl': 1.10.4 - '@swc/core-win32-arm64-msvc': 1.10.4 - '@swc/core-win32-ia32-msvc': 1.10.4 - '@swc/core-win32-x64-msvc': 1.10.4 + '@swc/core-darwin-arm64': 1.10.9 + '@swc/core-darwin-x64': 1.10.9 + '@swc/core-linux-arm-gnueabihf': 1.10.9 + '@swc/core-linux-arm64-gnu': 1.10.9 + '@swc/core-linux-arm64-musl': 1.10.9 + '@swc/core-linux-x64-gnu': 1.10.9 + '@swc/core-linux-x64-musl': 1.10.9 + '@swc/core-win32-arm64-msvc': 1.10.9 + '@swc/core-win32-ia32-msvc': 1.10.9 + '@swc/core-win32-x64-msvc': 1.10.9 '@swc/counter@0.1.3': {} @@ -8703,52 +8735,52 @@ snapshots: '@types/aws4@1.11.6': dependencies: - '@types/node': 20.17.11 + '@types/node': 22.10.9 '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.26.3 - '@babel/types': 7.26.3 + '@babel/parser': 7.26.7 + '@babel/types': 7.26.7 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.6 '@types/babel__generator@7.6.8': dependencies: - '@babel/types': 7.26.3 + '@babel/types': 7.26.7 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.26.3 - '@babel/types': 7.26.3 + '@babel/parser': 7.26.7 + '@babel/types': 7.26.7 '@types/babel__traverse@7.20.6': dependencies: - '@babel/types': 7.26.3 + '@babel/types': 7.26.7 '@types/better-sqlite3@7.6.12': dependencies: - '@types/node': 20.17.11 + '@types/node': 22.10.9 '@types/breejs__later@4.1.5': {} '@types/bunyan@1.8.11': dependencies: - '@types/node': 20.17.11 + '@types/node': 22.10.9 '@types/bunyan@1.8.9': dependencies: - '@types/node': 20.17.11 + '@types/node': 22.10.9 '@types/cacache@17.0.2': dependencies: - '@types/node': 20.17.11 + '@types/node': 22.10.9 '@types/cacheable-request@6.0.3': dependencies: '@types/http-cache-semantics': 4.0.4 '@types/keyv': 3.1.4 - '@types/node': 20.17.11 + '@types/node': 22.10.9 '@types/responselike': 1.0.3 '@types/callsite@1.0.34': {} @@ -8765,9 +8797,9 @@ snapshots: dependencies: '@types/ms': 0.7.34 - '@types/diff@6.0.0': {} + '@types/diff@7.0.0': {} - '@types/emscripten@1.39.13': {} + '@types/emscripten@1.40.0': {} '@types/eslint@8.56.12': dependencies: @@ -8779,7 +8811,7 @@ snapshots: '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 20.17.11 + '@types/node': 22.10.9 '@types/git-url-parse@9.0.3': {} @@ -8789,7 +8821,7 @@ snapshots: '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 20.17.11 + '@types/node': 22.10.9 '@types/http-cache-semantics@4.0.4': {} @@ -8815,19 +8847,19 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 20.17.11 + '@types/node': 22.10.9 '@types/katex@0.16.7': {} '@types/keyv@3.1.4': dependencies: - '@types/node': 20.17.11 + '@types/node': 22.10.9 '@types/linkify-it@5.0.0': {} '@types/linkify-markdown@1.0.3': {} - '@types/lodash@4.17.13': {} + '@types/lodash@4.17.14': {} '@types/luxon@3.4.2': {} @@ -8840,7 +8872,7 @@ snapshots: '@types/marshal@0.5.3': dependencies: - '@types/node': 20.17.11 + '@types/node': 22.10.9 '@types/mdast@3.0.15': dependencies: @@ -8850,15 +8882,15 @@ snapshots: '@types/minimist@1.2.5': {} - '@types/moo@0.5.5': {} + '@types/moo@0.5.10': {} - '@types/moo@0.5.9': {} + '@types/moo@0.5.5': {} '@types/ms@0.7.34': {} - '@types/node@20.17.11': + '@types/node@22.10.9': dependencies: - undici-types: 6.19.8 + undici-types: 6.20.0 '@types/normalize-package-data@2.4.4': {} @@ -8870,7 +8902,7 @@ snapshots: '@types/responselike@1.0.3': dependencies: - '@types/node': 20.17.11 + '@types/node': 22.10.9 '@types/semver-stable@3.0.2': {} @@ -8890,7 +8922,7 @@ snapshots: '@types/tar@6.1.13': dependencies: - '@types/node': 20.17.11 + '@types/node': 22.10.9 minipass: 4.2.8 '@types/tmp@0.2.6': {} @@ -8915,43 +8947,43 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 20.17.11 + '@types/node': 22.10.9 optional: true - '@typescript-eslint/eslint-plugin@8.19.0(@typescript-eslint/parser@8.19.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2)': + '@typescript-eslint/eslint-plugin@8.20.0(@typescript-eslint/parser@8.20.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.19.0(eslint@8.57.1)(typescript@5.7.2) - '@typescript-eslint/scope-manager': 8.19.0 - '@typescript-eslint/type-utils': 8.19.0(eslint@8.57.1)(typescript@5.7.2) - '@typescript-eslint/utils': 8.19.0(eslint@8.57.1)(typescript@5.7.2) - '@typescript-eslint/visitor-keys': 8.19.0 + '@typescript-eslint/parser': 8.20.0(eslint@8.57.1)(typescript@5.7.3) + '@typescript-eslint/scope-manager': 8.20.0 + '@typescript-eslint/type-utils': 8.20.0(eslint@8.57.1)(typescript@5.7.3) + '@typescript-eslint/utils': 8.20.0(eslint@8.57.1)(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.20.0 eslint: 8.57.1 graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 1.4.3(typescript@5.7.2) - typescript: 5.7.2 + ts-api-utils: 2.0.0(typescript@5.7.3) + typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/experimental-utils@5.62.0(eslint@8.57.1)(typescript@5.7.2)': + '@typescript-eslint/experimental-utils@5.62.0(eslint@8.57.1)(typescript@5.7.3)': dependencies: - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.7.2) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.7.3) eslint: 8.57.1 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/parser@8.19.0(eslint@8.57.1)(typescript@5.7.2)': + '@typescript-eslint/parser@8.20.0(eslint@8.57.1)(typescript@5.7.3)': dependencies: - '@typescript-eslint/scope-manager': 8.19.0 - '@typescript-eslint/types': 8.19.0 - '@typescript-eslint/typescript-estree': 8.19.0(typescript@5.7.2) - '@typescript-eslint/visitor-keys': 8.19.0 + '@typescript-eslint/scope-manager': 8.20.0 + '@typescript-eslint/types': 8.20.0 + '@typescript-eslint/typescript-estree': 8.20.0(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.20.0 debug: 4.4.0 eslint: 8.57.1 - typescript: 5.7.2 + typescript: 5.7.3 transitivePeerDependencies: - supports-color @@ -8960,27 +8992,34 @@ snapshots: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - '@typescript-eslint/scope-manager@8.19.0': + '@typescript-eslint/scope-manager@8.20.0': + dependencies: + '@typescript-eslint/types': 8.20.0 + '@typescript-eslint/visitor-keys': 8.20.0 + + '@typescript-eslint/scope-manager@8.21.0': dependencies: - '@typescript-eslint/types': 8.19.0 - '@typescript-eslint/visitor-keys': 8.19.0 + '@typescript-eslint/types': 8.21.0 + '@typescript-eslint/visitor-keys': 8.21.0 - '@typescript-eslint/type-utils@8.19.0(eslint@8.57.1)(typescript@5.7.2)': + '@typescript-eslint/type-utils@8.20.0(eslint@8.57.1)(typescript@5.7.3)': dependencies: - '@typescript-eslint/typescript-estree': 8.19.0(typescript@5.7.2) - '@typescript-eslint/utils': 8.19.0(eslint@8.57.1)(typescript@5.7.2) + '@typescript-eslint/typescript-estree': 8.20.0(typescript@5.7.3) + '@typescript-eslint/utils': 8.20.0(eslint@8.57.1)(typescript@5.7.3) debug: 4.4.0 eslint: 8.57.1 - ts-api-utils: 1.4.3(typescript@5.7.2) - typescript: 5.7.2 + ts-api-utils: 2.0.0(typescript@5.7.3) + typescript: 5.7.3 transitivePeerDependencies: - supports-color '@typescript-eslint/types@5.62.0': {} - '@typescript-eslint/types@8.19.0': {} + '@typescript-eslint/types@8.20.0': {} - '@typescript-eslint/typescript-estree@5.62.0(typescript@5.7.2)': + '@typescript-eslint/types@8.21.0': {} + + '@typescript-eslint/typescript-estree@5.62.0(typescript@5.7.3)': dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 @@ -8988,34 +9027,48 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 semver: 7.6.3 - tsutils: 3.21.0(typescript@5.7.2) + tsutils: 3.21.0(typescript@5.7.3) optionalDependencies: - typescript: 5.7.2 + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@8.20.0(typescript@5.7.3)': + dependencies: + '@typescript-eslint/types': 8.20.0 + '@typescript-eslint/visitor-keys': 8.20.0 + debug: 4.4.0 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 2.0.0(typescript@5.7.3) + typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.19.0(typescript@5.7.2)': + '@typescript-eslint/typescript-estree@8.21.0(typescript@5.7.3)': dependencies: - '@typescript-eslint/types': 8.19.0 - '@typescript-eslint/visitor-keys': 8.19.0 + '@typescript-eslint/types': 8.21.0 + '@typescript-eslint/visitor-keys': 8.21.0 debug: 4.4.0 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.3 - ts-api-utils: 1.4.3(typescript@5.7.2) - typescript: 5.7.2 + ts-api-utils: 2.0.0(typescript@5.7.3) + typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@5.7.2)': + '@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@5.7.3)': dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) '@types/json-schema': 7.0.15 '@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.7.2) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.7.3) eslint: 8.57.1 eslint-scope: 5.1.1 semver: 7.6.3 @@ -9023,14 +9076,25 @@ snapshots: - supports-color - typescript - '@typescript-eslint/utils@8.19.0(eslint@8.57.1)(typescript@5.7.2)': + '@typescript-eslint/utils@8.20.0(eslint@8.57.1)(typescript@5.7.3)': dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) - '@typescript-eslint/scope-manager': 8.19.0 - '@typescript-eslint/types': 8.19.0 - '@typescript-eslint/typescript-estree': 8.19.0(typescript@5.7.2) + '@typescript-eslint/scope-manager': 8.20.0 + '@typescript-eslint/types': 8.20.0 + '@typescript-eslint/typescript-estree': 8.20.0(typescript@5.7.3) eslint: 8.57.1 - typescript: 5.7.2 + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.21.0(eslint@8.57.1)(typescript@5.7.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@typescript-eslint/scope-manager': 8.21.0 + '@typescript-eslint/types': 8.21.0 + '@typescript-eslint/typescript-estree': 8.21.0(typescript@5.7.3) + eslint: 8.57.1 + typescript: 5.7.3 transitivePeerDependencies: - supports-color @@ -9039,12 +9103,17 @@ snapshots: '@typescript-eslint/types': 5.62.0 eslint-visitor-keys: 3.4.3 - '@typescript-eslint/visitor-keys@8.19.0': + '@typescript-eslint/visitor-keys@8.20.0': + dependencies: + '@typescript-eslint/types': 8.20.0 + eslint-visitor-keys: 4.2.0 + + '@typescript-eslint/visitor-keys@8.21.0': dependencies: - '@typescript-eslint/types': 8.19.0 + '@typescript-eslint/types': 8.21.0 eslint-visitor-keys: 4.2.0 - '@ungap/structured-clone@1.2.1': {} + '@ungap/structured-clone@1.3.0': {} '@yarnpkg/core@4.2.0(typanion@3.14.0)': dependencies: @@ -9083,7 +9152,7 @@ snapshots: '@yarnpkg/libzip@3.1.0(@yarnpkg/fslib@3.1.1)': dependencies: - '@types/emscripten': 1.39.13 + '@types/emscripten': 1.40.0 '@yarnpkg/fslib': 3.1.1 tslib: 2.8.1 @@ -9206,7 +9275,7 @@ snapshots: call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.23.9 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 get-intrinsic: 1.2.7 is-string: 1.1.1 @@ -9218,7 +9287,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.23.9 es-errors: 1.3.0 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 es-shim-unscopables: 1.0.2 array.prototype.flat@1.3.3: @@ -9247,6 +9316,8 @@ snapshots: arrify@1.0.1: {} + async-function@1.0.0: {} + async-mutex@0.5.0: dependencies: tslib: 2.8.1 @@ -9272,13 +9343,13 @@ snapshots: tunnel: 0.0.6 typed-rest-client: 2.1.0 - babel-jest@29.7.0(@babel/core@7.26.0): + babel-jest@29.7.0(@babel/core@7.26.7): dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@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.26.0) + babel-preset-jest: 29.6.3(@babel/core@7.26.7) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -9287,7 +9358,7 @@ snapshots: babel-plugin-istanbul@6.1.1: dependencies: - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-instrument: 5.2.1 @@ -9298,34 +9369,34 @@ snapshots: babel-plugin-jest-hoist@29.6.3: dependencies: '@babel/template': 7.25.9 - '@babel/types': 7.26.3 + '@babel/types': 7.26.7 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.6 - babel-preset-current-node-syntax@1.1.0(@babel/core@7.26.0): - dependencies: - '@babel/core': 7.26.0 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.26.0) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.26.0) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.26.0) - '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.0) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.26.0) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.26.0) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.26.0) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.26.0) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.0) - - babel-preset-jest@29.6.3(@babel/core@7.26.0): - dependencies: - '@babel/core': 7.26.0 + babel-preset-current-node-syntax@1.1.0(@babel/core@7.26.7): + dependencies: + '@babel/core': 7.26.7 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.26.7) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.26.7) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.26.7) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.26.7) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.7) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.26.7) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.26.7) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.26.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.26.7) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.26.7) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.26.7) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.26.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.7) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.26.7) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.7) + + babel-preset-jest@29.6.3(@babel/core@7.26.7): + dependencies: + '@babel/core': 7.26.7 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.0) + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.7) backslash@0.2.0: {} @@ -9339,10 +9410,10 @@ snapshots: before-after-hook@3.0.2: {} - better-sqlite3@11.7.0: + better-sqlite3@11.8.1: dependencies: bindings: 1.5.0 - prebuild-install: 7.1.2 + prebuild-install: 7.1.3 optional: true bignumber.js@9.1.2: {} @@ -9382,12 +9453,12 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.24.3: + browserslist@4.24.4: dependencies: - caniuse-lite: 1.0.30001690 - electron-to-chromium: 1.5.76 + caniuse-lite: 1.0.30001695 + electron-to-chromium: 1.5.88 node-releases: 2.0.19 - update-browserslist-db: 1.1.1(browserslist@4.24.3) + update-browserslist-db: 1.1.2(browserslist@4.24.4) bs-logger@0.2.6: dependencies: @@ -9409,6 +9480,10 @@ snapshots: ieee754: 1.2.1 optional: true + builtins@5.1.0: + dependencies: + semver: 7.6.3 + bunyan@1.8.15: {} bzip-deflate@1.0.0: {} @@ -9494,7 +9569,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001690: {} + caniuse-lite@1.0.30001695: {} chalk@2.4.2: dependencies: @@ -9612,7 +9687,7 @@ snapshots: commander@11.1.0: {} - commander@12.1.0: {} + commander@13.1.0: {} commander@8.3.0: {} @@ -9667,26 +9742,26 @@ snapshots: convert-source-map@2.0.0: {} - core-js-pure@3.39.0: {} + core-js-pure@3.40.0: {} core-util-is@1.0.3: {} - cosmiconfig@9.0.0(typescript@5.7.2): + cosmiconfig@9.0.0(typescript@5.7.3): dependencies: env-paths: 2.2.1 import-fresh: 3.3.0 js-yaml: 4.1.0 parse-json: 5.2.0 optionalDependencies: - typescript: 5.7.2 + typescript: 5.7.3 - create-jest@29.7.0(@types/node@20.17.11)(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2)): + create-jest@29.7.0(@types/node@22.10.9)(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3)): 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@20.17.11)(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2)) + jest-config: 29.7.0(@types/node@22.10.9)(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -9699,7 +9774,7 @@ snapshots: croner@9.0.0: {} - cronstrue@2.52.0: {} + cronstrue@2.53.0: {} cross-spawn@7.0.6: dependencies: @@ -9716,7 +9791,7 @@ snapshots: boolbase: 1.0.0 css-what: 6.1.0 domhandler: 5.0.3 - domutils: 3.2.1 + domutils: 3.2.2 nth-check: 2.1.1 css-what@6.1.0: {} @@ -9844,7 +9919,7 @@ snapshots: dependencies: domelementtype: 2.3.0 - domutils@3.2.1: + domutils@3.2.2: dependencies: dom-serializer: 2.0.0 domelementtype: 2.3.0 @@ -9883,7 +9958,7 @@ snapshots: dependencies: jake: 10.9.2 - electron-to-chromium@1.5.76: {} + electron-to-chromium@1.5.88: {} email-addresses@5.0.0: {} @@ -9949,7 +10024,7 @@ snapshots: data-view-byte-offset: 1.0.1 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 es-set-tostringtag: 2.1.0 es-to-primitive: 1.3.0 function.prototype.name: 1.1.8 @@ -9995,7 +10070,7 @@ snapshots: es-errors@1.3.0: {} - es-object-atoms@1.0.0: + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -10056,27 +10131,27 @@ snapshots: enhanced-resolve: 5.18.0 eslint: 8.57.1 fast-glob: 3.3.3 - get-tsconfig: 4.8.1 + get-tsconfig: 4.10.0 is-bun-module: 1.3.0 is-glob: 4.0.3 stable-hash: 0.0.4 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.19.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.20.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.19.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.20.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.19.0(eslint@8.57.1)(typescript@5.7.2) + '@typescript-eslint/parser': 8.20.0(eslint@8.57.1)(typescript@5.7.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.19.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.20.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -10087,7 +10162,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.19.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.20.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -10099,7 +10174,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.19.0(eslint@8.57.1)(typescript@5.7.2) + '@typescript-eslint/parser': 8.20.0(eslint@8.57.1)(typescript@5.7.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -10109,13 +10184,13 @@ snapshots: dependencies: eslint: 8.57.1 - eslint-plugin-jest@28.10.0(@typescript-eslint/eslint-plugin@8.19.0(@typescript-eslint/parser@8.19.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(jest@29.7.0(@types/node@20.17.11)(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2)))(typescript@5.7.2): + eslint-plugin-jest@28.11.0(@typescript-eslint/eslint-plugin@8.20.0(@typescript-eslint/parser@8.20.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(jest@29.7.0(@types/node@22.10.9)(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3)))(typescript@5.7.3): dependencies: - '@typescript-eslint/utils': 8.19.0(eslint@8.57.1)(typescript@5.7.2) + '@typescript-eslint/utils': 8.21.0(eslint@8.57.1)(typescript@5.7.3) eslint: 8.57.1 optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.19.0(@typescript-eslint/parser@8.19.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2) - jest: 29.7.0(@types/node@20.17.11)(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2)) + '@typescript-eslint/eslint-plugin': 8.20.0(@typescript-eslint/parser@8.20.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3) + jest: 29.7.0(@types/node@22.10.9)(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3)) transitivePeerDependencies: - supports-color - typescript @@ -10125,9 +10200,9 @@ snapshots: '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) eslint: 8.57.1 - eslint-plugin-typescript-enum@2.1.0(eslint@8.57.1)(typescript@5.7.2): + eslint-plugin-typescript-enum@2.1.0(eslint@8.57.1)(typescript@5.7.3): dependencies: - '@typescript-eslint/experimental-utils': 5.62.0(eslint@8.57.1)(typescript@5.7.2) + '@typescript-eslint/experimental-utils': 5.62.0(eslint@8.57.1)(typescript@5.7.3) transitivePeerDependencies: - eslint - supports-color @@ -10156,7 +10231,7 @@ snapshots: '@humanwhocodes/config-array': 0.13.0 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.2.1 + '@ungap/structured-clone': 1.3.0 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 @@ -10387,7 +10462,7 @@ snapshots: flatted@3.3.2: {} - for-each@0.3.3: + for-each@0.3.4: dependencies: is-callable: 1.2.7 @@ -10419,6 +10494,12 @@ snapshots: jsonfile: 6.1.0 universalify: 2.0.1 + fs-extra@11.3.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + fs-minipass@2.1.0: dependencies: minipass: 3.3.6 @@ -10477,7 +10558,7 @@ snapshots: call-bind-apply-helpers: 1.0.1 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 function-bind: 1.1.2 get-proto: 1.0.1 gopd: 1.2.0 @@ -10490,7 +10571,7 @@ snapshots: get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 get-stream@5.2.0: dependencies: @@ -10513,7 +10594,7 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.2.7 - get-tsconfig@4.8.1: + get-tsconfig@4.10.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -10565,7 +10646,7 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - glob@11.0.0: + glob@11.0.1: dependencies: foreground-child: 3.3.0 jackspeak: 4.0.2 @@ -10628,7 +10709,7 @@ snapshots: klona: 2.0.6 moo: 0.5.2 - google-auth-library@9.15.0(encoding@0.1.13): + google-auth-library@9.15.1(encoding@0.1.13): dependencies: base64-js: 1.5.1 ecdsa-sig-formatter: 1.0.11 @@ -10660,7 +10741,7 @@ snapshots: graceful-fs@4.2.11: {} - graph-data-structure@4.3.0: {} + graph-data-structure@4.3.1: {} grapheme-splitter@1.0.4: {} @@ -10797,7 +10878,7 @@ snapshots: ignore@5.3.2: {} - ignore@6.0.2: {} + ignore@7.0.3: {} immediate@3.0.6: {} @@ -10806,13 +10887,6 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 - import-from-esm@1.3.4: - dependencies: - debug: 4.4.0 - import-meta-resolve: 4.1.0 - transitivePeerDependencies: - - supports-color - import-from-esm@2.0.0: dependencies: debug: 4.4.0 @@ -10900,8 +10974,9 @@ snapshots: is-arrayish@0.2.1: {} - is-async-function@2.1.0: + is-async-function@2.1.1: dependencies: + async-function: 1.0.0 call-bound: 1.0.3 get-proto: 1.0.1 has-tostringtag: 1.0.2 @@ -11069,8 +11144,8 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: - '@babel/core': 7.26.0 - '@babel/parser': 7.26.3 + '@babel/core': 7.26.7 + '@babel/parser': 7.26.7 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -11079,8 +11154,8 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.26.0 - '@babel/parser': 7.26.3 + '@babel/core': 7.26.7 + '@babel/parser': 7.26.7 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 7.6.3 @@ -11146,7 +11221,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.11 + '@types/node': 22.10.9 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.3 @@ -11166,16 +11241,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.17.11)(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2)): + jest-cli@29.7.0(@types/node@22.10.9)(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2)) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.17.11)(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2)) + create-jest: 29.7.0(@types/node@22.10.9)(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.17.11)(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2)) + jest-config: 29.7.0(@types/node@22.10.9)(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -11185,12 +11260,12 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@20.17.11)(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2)): + jest-config@29.7.0(@types/node@22.10.9)(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3)): dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.0) + babel-jest: 29.7.0(@babel/core@7.26.7) chalk: 4.1.2 ci-info: 3.9.0 deepmerge: 4.3.1 @@ -11210,8 +11285,8 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 20.17.11 - ts-node: 10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2) + '@types/node': 22.10.9 + ts-node: 10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -11240,16 +11315,16 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.11 + '@types/node': 22.10.9 jest-mock: 29.7.0 jest-util: 29.7.0 - jest-extended@4.0.2(jest@29.7.0(@types/node@20.17.11)(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2))): + jest-extended@4.0.2(jest@29.7.0(@types/node@22.10.9)(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3))): dependencies: jest-diff: 29.7.0 jest-get-type: 29.6.3 optionalDependencies: - jest: 29.7.0(@types/node@20.17.11)(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2)) + jest: 29.7.0(@types/node@22.10.9)(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3)) jest-get-type@29.6.3: {} @@ -11257,7 +11332,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 20.17.11 + '@types/node': 22.10.9 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -11300,16 +11375,16 @@ snapshots: slash: 3.0.0 stack-utils: 2.0.6 - jest-mock-extended@3.0.7(jest@29.7.0(@types/node@20.17.11)(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2)))(typescript@5.7.2): + jest-mock-extended@3.0.7(jest@29.7.0(@types/node@22.10.9)(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3)))(typescript@5.7.3): dependencies: - jest: 29.7.0(@types/node@20.17.11)(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2)) - ts-essentials: 10.0.4(typescript@5.7.2) - typescript: 5.7.2 + jest: 29.7.0(@types/node@22.10.9)(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3)) + ts-essentials: 10.0.4(typescript@5.7.3) + typescript: 5.7.3 jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.17.11 + '@types/node': 22.10.9 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -11344,7 +11419,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.11 + '@types/node': 22.10.9 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -11372,7 +11447,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.11 + '@types/node': 22.10.9 chalk: 4.1.2 cjs-module-lexer: 1.4.1 collect-v8-coverage: 1.0.2 @@ -11392,15 +11467,15 @@ snapshots: jest-snapshot@29.7.0: dependencies: - '@babel/core': 7.26.0 - '@babel/generator': 7.26.3 - '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0) - '@babel/types': 7.26.3 + '@babel/core': 7.26.7 + '@babel/generator': 7.26.5 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.7) + '@babel/types': 7.26.7 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.0) + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.7) chalk: 4.1.2 expect: 29.7.0 graceful-fs: 4.2.11 @@ -11418,7 +11493,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.17.11 + '@types/node': 22.10.9 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -11437,7 +11512,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.11 + '@types/node': 22.10.9 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -11446,17 +11521,17 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 20.17.11 + '@types/node': 22.10.9 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@20.17.11)(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2)): + jest@29.7.0(@types/node@22.10.9)(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2)) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.17.11)(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2)) + jest-cli: 29.7.0(@types/node@22.10.9)(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -11534,7 +11609,7 @@ snapshots: jwa: 2.0.0 safe-buffer: 5.2.1 - katex@0.16.19: + katex@0.16.21: dependencies: commander: 8.3.0 @@ -11621,7 +11696,7 @@ snapshots: lodash@4.17.21: {} - long@5.2.3: {} + long@5.2.4: {} longest-streak@2.0.4: {} @@ -11690,22 +11765,22 @@ snapshots: dependencies: repeat-string: 1.6.1 - markdownlint-cli2-formatter-default@0.0.5(markdownlint-cli2@0.17.1): + markdownlint-cli2-formatter-default@0.0.5(markdownlint-cli2@0.17.2): dependencies: - markdownlint-cli2: 0.17.1 + markdownlint-cli2: 0.17.2 - markdownlint-cli2@0.17.1: + markdownlint-cli2@0.17.2: dependencies: globby: 14.0.2 js-yaml: 4.1.0 jsonc-parser: 3.3.1 - markdownlint: 0.37.3 - markdownlint-cli2-formatter-default: 0.0.5(markdownlint-cli2@0.17.1) + markdownlint: 0.37.4 + markdownlint-cli2-formatter-default: 0.0.5(markdownlint-cli2@0.17.2) micromatch: 4.0.8 transitivePeerDependencies: - supports-color - markdownlint@0.37.3: + markdownlint@0.37.4: dependencies: markdown-it: 14.1.0 micromark: 4.0.1 @@ -11769,7 +11844,7 @@ snapshots: mdurl@2.0.0: {} - memfs@4.15.2: + memfs@4.17.0: dependencies: '@jsonjoy.com/json-pack': 1.1.1(tslib@2.8.1) '@jsonjoy.com/util': 1.5.0(tslib@2.8.1) @@ -11827,7 +11902,7 @@ snapshots: micromark-util-html-tag-name: 2.0.1 micromark-util-normalize-identifier: 2.0.1 micromark-util-resolve-all: 2.0.1 - micromark-util-subtokenize: 2.0.3 + micromark-util-subtokenize: 2.0.4 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.1 @@ -11871,7 +11946,7 @@ snapshots: dependencies: '@types/katex': 0.16.7 devlop: 1.1.0 - katex: 0.16.19 + katex: 0.16.21 micromark-factory-space: 2.0.1 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 @@ -11951,7 +12026,7 @@ snapshots: micromark-util-encode: 2.0.1 micromark-util-symbol: 2.0.1 - micromark-util-subtokenize@2.0.3: + micromark-util-subtokenize@2.0.4: dependencies: devlop: 1.1.0 micromark-util-chunked: 2.0.1 @@ -11985,7 +12060,7 @@ snapshots: micromark-util-normalize-identifier: 2.0.1 micromark-util-resolve-all: 2.0.1 micromark-util-sanitize-uri: 2.0.1 - micromark-util-subtokenize: 2.0.3 + micromark-util-subtokenize: 2.0.4 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.1 transitivePeerDependencies: @@ -12112,7 +12187,7 @@ snapshots: nanoid@3.3.8: {} - napi-build-utils@1.0.2: + napi-build-utils@2.0.0: optional: true natural-compare@1.4.0: {} @@ -12142,7 +12217,7 @@ snapshots: transitivePeerDependencies: - supports-color - node-abi@3.71.0: + node-abi@3.73.0: dependencies: semver: 7.6.3 optional: true @@ -12176,7 +12251,7 @@ snapshots: - supports-color optional: true - node-html-parser@6.1.13: + node-html-parser@7.0.1: dependencies: css-select: 5.1.0 he: 1.2.0 @@ -12295,7 +12370,7 @@ snapshots: call-bind: 1.0.8 call-bound: 1.0.3 define-properties: 1.2.1 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 has-symbols: 1.1.0 object-keys: 1.1.1 @@ -12304,7 +12379,7 @@ snapshots: call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.23.9 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 object.groupby@1.0.3: dependencies: @@ -12317,7 +12392,7 @@ snapshots: call-bind: 1.0.8 call-bound: 1.0.3 define-properties: 1.2.1 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 once@1.4.0: dependencies: @@ -12470,7 +12545,7 @@ snapshots: dependencies: '@babel/code-frame': 7.26.2 index-to-position: 0.1.2 - type-fest: 4.31.0 + type-fest: 4.33.0 parse-link-header@2.0.0: dependencies: @@ -12551,19 +12626,19 @@ snapshots: possible-typed-array-names@1.0.0: {} - prebuild-install@7.1.2: + prebuild-install@7.1.3: dependencies: 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.71.0 + napi-build-utils: 2.0.0 + node-abi: 3.73.0 pump: 3.0.2 rc: 1.2.8 simple-get: 4.0.1 - tar-fs: 2.1.1 + tar-fs: 2.1.2 tunnel-agent: 0.6.0 optional: true @@ -12619,8 +12694,8 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 20.17.11 - long: 5.2.3 + '@types/node': 22.10.9 + long: 5.2.4 protocols@2.0.1: {} @@ -12637,7 +12712,7 @@ snapshots: purepack@1.0.6: {} - qs@6.13.1: + qs@6.14.0: dependencies: side-channel: 1.1.0 @@ -12674,7 +12749,7 @@ snapshots: dependencies: find-up-simple: 1.0.0 read-pkg: 9.0.1 - type-fest: 4.31.0 + type-fest: 4.33.0 read-pkg-up@7.0.1: dependencies: @@ -12694,7 +12769,7 @@ snapshots: '@types/normalize-package-data': 2.4.4 normalize-package-data: 6.0.2 parse-json: 8.1.0 - type-fest: 4.31.0 + type-fest: 4.33.0 unicorn-magic: 0.1.0 read-yaml-file@2.1.0: @@ -12738,7 +12813,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.23.9 es-errors: 1.3.0 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 get-intrinsic: 1.2.7 get-proto: 1.0.1 which-builtin-type: 1.2.1 @@ -12839,7 +12914,7 @@ snapshots: rimraf@6.0.1: dependencies: - glob: 11.0.0 + glob: 11.0.1 package-json-from-dist: 1.0.1 roarr@2.15.4: @@ -12885,15 +12960,15 @@ snapshots: sax@1.4.1: {} - semantic-release@24.2.0(typescript@5.7.2): + semantic-release@24.2.1(typescript@5.7.3): dependencies: - '@semantic-release/commit-analyzer': 13.0.1(semantic-release@24.2.0(typescript@5.7.2)) + '@semantic-release/commit-analyzer': 13.0.1(semantic-release@24.2.1(typescript@5.7.3)) '@semantic-release/error': 4.0.0 - '@semantic-release/github': 11.0.1(semantic-release@24.2.0(typescript@5.7.2)) - '@semantic-release/npm': 12.0.1(semantic-release@24.2.0(typescript@5.7.2)) - '@semantic-release/release-notes-generator': 14.0.3(semantic-release@24.2.0(typescript@5.7.2)) + '@semantic-release/github': 11.0.1(semantic-release@24.2.1(typescript@5.7.3)) + '@semantic-release/npm': 12.0.1(semantic-release@24.2.1(typescript@5.7.3)) + '@semantic-release/release-notes-generator': 14.0.3(semantic-release@24.2.1(typescript@5.7.3)) aggregate-error: 5.0.0 - cosmiconfig: 9.0.0(typescript@5.7.2) + cosmiconfig: 9.0.0(typescript@5.7.3) debug: 4.4.0 env-ci: 11.1.0 execa: 9.5.2 @@ -12903,7 +12978,7 @@ snapshots: git-log-parser: 1.2.1 hook-std: 3.0.0 hosted-git-info: 8.0.2 - import-from-esm: 1.3.4 + import-from-esm: 2.0.0 lodash-es: 4.17.21 marked: 12.0.2 marked-terminal: 7.2.1(marked@12.0.2) @@ -12966,7 +13041,7 @@ snapshots: dependencies: dunder-proto: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 shebang-command@2.0.0: dependencies: @@ -13105,16 +13180,16 @@ snapshots: spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.20 + spdx-license-ids: 3.0.21 spdx-exceptions@2.5.0: {} spdx-expression-parse@3.0.1: dependencies: spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.20 + spdx-license-ids: 3.0.21 - spdx-license-ids@3.0.20: {} + spdx-license-ids@3.0.21: {} split2@1.0.0: dependencies: @@ -13172,7 +13247,7 @@ snapshots: define-data-property: 1.1.4 define-properties: 1.2.1 es-abstract: 1.23.9 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 has-property-descriptors: 1.0.2 string.prototype.trimend@1.0.9: @@ -13180,13 +13255,13 @@ snapshots: call-bind: 1.0.8 call-bound: 1.0.3 define-properties: 1.2.1 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 string.prototype.trimstart@1.0.8: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 string_decoder@1.1.1: dependencies: @@ -13252,7 +13327,7 @@ snapshots: tapable@2.2.1: {} - tar-fs@2.1.1: + tar-fs@2.1.2: dependencies: chownr: 1.1.4 mkdirp-classic: 0.5.3 @@ -13374,52 +13449,52 @@ snapshots: trough@1.0.5: {} - ts-api-utils@1.4.3(typescript@5.7.2): + ts-api-utils@2.0.0(typescript@5.7.3): dependencies: - typescript: 5.7.2 + typescript: 5.7.3 - ts-essentials@10.0.4(typescript@5.7.2): + ts-essentials@10.0.4(typescript@5.7.3): optionalDependencies: - typescript: 5.7.2 + typescript: 5.7.3 - ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.11)(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2)))(typescript@5.7.2): + ts-jest@29.2.5(@babel/core@7.26.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.7))(jest@29.7.0(@types/node@22.10.9)(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3)))(typescript@5.7.3): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.17.11)(ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2)) + jest: 29.7.0(@types/node@22.10.9)(ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 semver: 7.6.3 - typescript: 5.7.2 + typescript: 5.7.3 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.0) + babel-jest: 29.7.0(@babel/core@7.26.7) - ts-node@10.9.2(@swc/core@1.10.4)(@types/node@20.17.11)(typescript@5.7.2): + ts-node@10.9.2(@swc/core@1.10.9)(@types/node@22.10.9)(typescript@5.7.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.17.11 + '@types/node': 22.10.9 acorn: 8.14.0 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.7.2 + typescript: 5.7.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: - '@swc/core': 1.10.4 + '@swc/core': 1.10.9 tsconfig-paths@3.15.0: dependencies: @@ -13432,10 +13507,10 @@ snapshots: tslib@2.8.1: {} - tsutils@3.21.0(typescript@5.7.2): + tsutils@3.21.0(typescript@5.7.3): dependencies: tslib: 1.14.1 - typescript: 5.7.2 + typescript: 5.7.3 tunnel-agent@0.6.0: dependencies: @@ -13474,7 +13549,7 @@ snapshots: type-fest@2.19.0: {} - type-fest@4.31.0: {} + type-fest@4.33.0: {} typed-array-buffer@1.0.3: dependencies: @@ -13485,7 +13560,7 @@ snapshots: typed-array-byte-length@1.0.3: dependencies: call-bind: 1.0.8 - for-each: 0.3.3 + for-each: 0.3.4 gopd: 1.2.0 has-proto: 1.2.0 is-typed-array: 1.1.15 @@ -13494,7 +13569,7 @@ snapshots: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.8 - for-each: 0.3.3 + for-each: 0.3.4 gopd: 1.2.0 has-proto: 1.2.0 is-typed-array: 1.1.15 @@ -13503,7 +13578,7 @@ snapshots: typed-array-length@1.0.7: dependencies: call-bind: 1.0.8 - for-each: 0.3.3 + for-each: 0.3.4 gopd: 1.2.0 is-typed-array: 1.1.15 possible-typed-array-names: 1.0.0 @@ -13513,7 +13588,7 @@ snapshots: dependencies: des.js: 1.1.0 js-md4: 0.3.2 - qs: 6.13.1 + qs: 6.14.0 tunnel: 0.0.6 underscore: 1.13.7 @@ -13521,7 +13596,7 @@ snapshots: dependencies: is-typedarray: 1.0.0 - typescript@5.7.2: {} + typescript@5.7.3: {} uc.micro@2.1.0: {} @@ -13538,7 +13613,7 @@ snapshots: underscore@1.13.7: {} - undici-types@6.19.8: {} + undici-types@6.20.0: {} unicode-emoji-modifier-base@1.0.0: {} @@ -13603,9 +13678,9 @@ snapshots: upath@2.0.1: {} - update-browserslist-db@1.1.1(browserslist@4.24.3): + update-browserslist-db@1.1.2(browserslist@4.24.4): dependencies: - browserslist: 4.24.3 + browserslist: 4.24.4 escalade: 3.2.0 picocolors: 1.1.1 @@ -13644,6 +13719,10 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 + validate-npm-package-name@5.0.0: + dependencies: + builtins: 5.1.0 + validate-npm-package-name@6.0.0: {} vfile-message@2.0.4: @@ -13684,7 +13763,7 @@ snapshots: call-bound: 1.0.3 function.prototype.name: 1.1.8 has-tostringtag: 1.0.2 - is-async-function: 2.1.0 + is-async-function: 2.1.1 is-date-object: 1.1.0 is-finalizationregistry: 1.1.1 is-generator-function: 1.1.0 @@ -13709,7 +13788,7 @@ snapshots: available-typed-arrays: 1.0.7 call-bind: 1.0.8 call-bound: 1.0.3 - for-each: 0.3.3 + for-each: 0.3.4 gopd: 1.2.0 has-tostringtag: 1.0.2 diff --git a/pyproject.toml b/pyproject.toml index 505881c624b713..ab231c3855bc30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] dependencies = [ - "mkdocs-material==9.5.49", + "mkdocs-material==9.6.1", "mkdocs-awesome-pages-plugin==2.10.1", ] requires-python = ">=3.11" diff --git a/test/documentation.spec.ts b/test/docs/documentation.spec.ts similarity index 97% rename from test/documentation.spec.ts rename to test/docs/documentation.spec.ts index 16f0c431cb2a83..427803b3388134 100644 --- a/test/documentation.spec.ts +++ b/test/docs/documentation.spec.ts @@ -1,12 +1,12 @@ import fs from 'fs-extra'; import { glob } from 'glob'; -import { getOptions } from '../lib/config/options'; -import { regEx } from '../lib/util/regex'; +import { getOptions } from '../../lib/config/options'; +import { regEx } from '../../lib/util/regex'; const options = getOptions(); const markdownGlob = '{docs,lib}/**/*.md'; -describe('documentation', () => { +describe('docs/documentation', () => { it('has no invalid links', async () => { const markdownFiles = await glob(markdownGlob); diff --git a/test/other/validate-docker.spec.ts b/test/other/validate-docker.spec.ts new file mode 100644 index 00000000000000..05bdc11a575079 --- /dev/null +++ b/test/other/validate-docker.spec.ts @@ -0,0 +1,20 @@ +import fs from 'fs-extra'; + +describe('other/validate-docker', () => { + it('validate dockerfile has consistent base image versions', async () => { + const dockerfile = await fs.readFile('tools/docker/Dockerfile', 'utf8'); + expect(dockerfile).toBeString(); + expect(dockerfile).toContain('ghcr.io/renovatebot/base-image'); + + const matches = dockerfile.matchAll( + /ghcr\.io\/renovatebot\/base-image:(?\d+\.\d+\.\d+)/g, + ); + + expect(matches).toBeDefined(); + + const versions = [...matches].map((v) => v.groups?.version); + + expect(versions).toHaveLength(3); + expect(new Set(versions)).toEqual(new Set(versions.slice(0, 1))); + }); +}); diff --git a/test/validate-schemas.spec.ts b/test/other/validate-schemas.spec.ts similarity index 90% rename from test/validate-schemas.spec.ts rename to test/other/validate-schemas.spec.ts index e023e476b6123b..d2b94311fa0ff4 100644 --- a/test/validate-schemas.spec.ts +++ b/test/other/validate-schemas.spec.ts @@ -1,10 +1,10 @@ import fs from 'fs-extra'; import upath from 'upath'; -import { Json } from '../lib/util/schema-utils'; -import { capitalize } from '../tools/docs/utils'; -import * as Schemas from '../tools/schemas/schema'; +import { Json } from '../../lib/util/schema-utils'; +import { capitalize } from '../../tools/docs/utils'; +import * as Schemas from '../../tools/schemas/schema'; -describe('validate-schemas', () => { +describe('other/validate-schemas', () => { it('validate json files in lib/data against their schemas', async () => { const dataFileDir = 'lib/data'; const schemaDir = 'tools/schemas'; diff --git a/test/setup.ts b/test/setup.ts index 4ee0a3ca978f26..6eddbf176d7aa8 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -13,4 +13,8 @@ jest.mock('../lib/modules/platform/scm', () => ({ scm: mockDeep(), })); -jest.mock('../lib/logger', () => mockDeep()); +jest.mock('../lib/logger', () => { + return mockDeep({ + withMeta: (_: Record, cb: () => T): T => cb(), + }); +}); diff --git a/tools/docker.ts b/tools/docker.ts index 42cd36647d7435..734b473ba35e20 100644 --- a/tools/docker.ts +++ b/tools/docker.ts @@ -16,6 +16,7 @@ program 'delay between tries for docker build (eg. 5s, 10m, 1h)', '30s', ) + .option('--args ', 'additional arguments to pass to docker build') .action(async (opts) => { logger.info('Building docker images ...'); await bake('build', opts); diff --git a/tools/docker/Dockerfile b/tools/docker/Dockerfile index 1f0a6900ef34c8..6b67d454c5c790 100644 --- a/tools/docker/Dockerfile +++ b/tools/docker/Dockerfile @@ -1,27 +1,27 @@ -# syntax=docker/dockerfile:1.12.1@sha256:93bfd3b68c109427185cd78b4779fc82b484b0b7618e36d0f104d4d801e66d25 +# syntax=docker/dockerfile:1.13.0@sha256:426b85b823c113372f766a963f68cfd9cd4878e1bcc0fda58779127ee98a28eb ARG BASE_IMAGE_TYPE=slim # -------------------------------------- # slim image # -------------------------------------- -FROM ghcr.io/renovatebot/base-image:9.29.1@sha256:db4b70c00fb197babca9dd92be612bef044d7a35d933d19c668864f84b52d1f8 AS slim-base +FROM ghcr.io/renovatebot/base-image:9.38.3@sha256:e4d31148559728ffb11e0b8e1ff4d52ebc35b9e1e0dc2be5f01075dae939012b AS slim-base # -------------------------------------- # full image # -------------------------------------- -FROM ghcr.io/renovatebot/base-image:9.29.1-full@sha256:4880c7aae10ed892d49c6c5573418014605ce2824c978dbcc04382a2c26bb0df AS full-base +FROM ghcr.io/renovatebot/base-image:9.38.3-full@sha256:27675cad452fda603cecbac9662bed944d707bd93457393ae263a7876807c58b AS full-base ENV RENOVATE_BINARY_SOURCE=global # -------------------------------------- # build image # -------------------------------------- -FROM --platform=$BUILDPLATFORM ghcr.io/renovatebot/base-image:9.29.1@sha256:db4b70c00fb197babca9dd92be612bef044d7a35d933d19c668864f84b52d1f8 AS build +FROM --platform=$BUILDPLATFORM ghcr.io/renovatebot/base-image:9.38.3@sha256:e4d31148559728ffb11e0b8e1ff4d52ebc35b9e1e0dc2be5f01075dae939012b AS build # We want a specific node version here # renovate: datasource=node-version -RUN install-tool node 22.11.0 +RUN install-tool node 22.13.1 WORKDIR /usr/local/renovate @@ -59,9 +59,10 @@ COPY --link pnpm-lock.yaml ./ # set `npm_config_arch` for `prebuild-install` # set `npm_config_platform_arch` for `install-artifact-from-github` +ENV npm_config_arch=${ARCH} npm_config_platform_arch=${ARCH} + # only fetch deps from lockfile https://pnpm.io/cli/fetch RUN set -ex; \ - export npm_config_arch=${ARCH} npm_config_platform_arch=${ARCH}; \ corepack pnpm fetch --prod; \ true @@ -97,7 +98,7 @@ RUN ln -sf /usr/local/renovate/node /bin/node # ensure default base and cache directories exist. RUN mkdir -p /tmp/renovate/cache && \ - chmod -R 777 /tmp/renovate + chmod -R 777 /tmp/renovate # test RUN set -ex; \ diff --git a/tools/docker/bin/renovate-entrypoint.sh b/tools/docker/bin/renovate-entrypoint.sh index 7e0d39a1779887..6962881d0d39b8 100755 --- a/tools/docker/bin/renovate-entrypoint.sh +++ b/tools/docker/bin/renovate-entrypoint.sh @@ -1,15 +1,5 @@ #!/bin/bash -if [[ -f "/usr/local/etc/env" && -z "${CONTAINERBASE_ENV+x}" ]]; then - # shellcheck source=/dev/null - . /usr/local/etc/env -fi - -if [[ ! -d "/tmp/containerbase" ]]; then - # initialize all prepared tools - containerbase-cli init tool all -fi - if [[ "${1:0:1}" = '-' ]]; then # assume $1 is renovate flag set -- renovate "$@" @@ -20,4 +10,5 @@ if [[ ! -x "$(command -v "${1}")" ]]; then set -- renovate "$@" fi -exec dumb-init -- "$@" +# call the original entrypoint +exec docker-entrypoint.sh "$@" diff --git a/tools/docs/github-query-items.ts b/tools/docs/github-query-items.ts index 13527c800eca6b..1683158541a861 100644 --- a/tools/docs/github-query-items.ts +++ b/tools/docs/github-query-items.ts @@ -63,7 +63,7 @@ export async function getOpenGitHubItems(): Promise { const per_page = 100; try { const query = getQueryString({ q, per_page }); - const res = await githubApi.getJson( + const res = await githubApi.getJsonUnchecked( gitHubApiUrl + query, { paginationField: 'items', diff --git a/tools/docs/manager.ts b/tools/docs/manager.ts index 73da0d3c09ff04..70b9c0a2435b72 100644 --- a/tools/docs/manager.ts +++ b/tools/docs/manager.ts @@ -48,6 +48,7 @@ export const CategoryNames: Record = { dotnet: '.NET', elixir: 'Elixir', golang: 'Go', + haskell: 'Haskell', helm: 'Helm', iac: 'Infrastructure as Code', java: 'Java', diff --git a/tools/docs/presets.ts b/tools/docs/presets.ts index 7c08b7eccffb1e..3a82d844fc46cd 100644 --- a/tools/docs/presets.ts +++ b/tools/docs/presets.ts @@ -70,7 +70,10 @@ export async function generatePresets(dist: string): Promise { if (presetDescription) { body += `\n\n${presetDescription}\n`; } else { - logger.warn(`Preset ${name}:${preset} has no description`); + logger.warn( + { preset: `${name}:${preset}` }, + 'Preset has no description', + ); } body += '\n```json\n'; body += JSON.stringify(value, null, 2); diff --git a/tools/docs/versioning.ts b/tools/docs/versioning.ts index 6982ed612faa65..c6712f2d6899c6 100644 --- a/tools/docs/versioning.ts +++ b/tools/docs/versioning.ts @@ -12,13 +12,13 @@ import { replaceContent, } from './utils'; -type Versioning = { +interface Versioning { id: string; displayName: string; - urls?: string[]; - supportsRanges?: boolean; + urls: string[]; + supportsRanges: boolean; supportedRangeStrategies?: string[]; -}; +} export async function generateVersioning( dist: string, diff --git a/tools/find-issues-with-missing-labels.sh b/tools/find-issues-with-missing-labels.sh index 3fe03b89a4fd29..4d0868dd36ec63 100755 --- a/tools/find-issues-with-missing-labels.sh +++ b/tools/find-issues-with-missing-labels.sh @@ -2,7 +2,6 @@ # When the repository labels are changed (i.e dropped a label, added a label, etc), you should make the same change to the lists below. # For example, if the repository added a "type:task" type label, then add "-label:type:task" to the TYPE_LABELS_FILTER. -TYPE_LABELS_FILTER='-label:type:bug -label:type:feature -label:type:docs -label:type:refactor -label:type:help' PRIORITY_LABELS_FILTER='-label:priority-1-critical -label:priority-2-high -label:priority-3-medium -label:priority-4-low' @@ -14,7 +13,7 @@ REPO='renovatebot/renovate' ISSUE_TITLE="Issues with missing labels" -for FILTER in "$TYPE_LABELS_FILTER" "$PRIORITY_LABELS_FILTER"; do +for FILTER in "$PRIORITY_LABELS_FILTER"; do # Extract the label type from the filter LABEL_TYPE=$(echo "$FILTER" | cut -d ':' -f 2 | cut -d '-' -f 1) @@ -40,6 +39,7 @@ done if [ "$HAS_ISSUES_MISSING_LABELS" = false ]; then echo "All checked issues have labels. Exiting the action." + ISSUE_BODY="$ISSUE_BODY All checked issues are correctly labeled.\n" exit 0 fi diff --git a/tools/mkdocs/mkdocs.yml b/tools/mkdocs/mkdocs.yml index 5b8eef98dd2962..cf68267748d9c9 100644 --- a/tools/mkdocs/mkdocs.yml +++ b/tools/mkdocs/mkdocs.yml @@ -59,7 +59,7 @@ theme: # The custom_dir points to the overrides folder, this folder has the code for our announcement bar. # The easiest way to disable the announcement bar is to comment out the custom_dir: overrides entry in this mkdocs.yml file. # https://squidfunk.github.io/mkdocs-material/customization/#setup-and-theme-structure - # custom_dir: overrides + custom_dir: overrides logo: 'assets/images/logo.png' favicon: 'assets/images/logo.png' diff --git a/tools/mkdocs/overrides/main.html b/tools/mkdocs/overrides/main.html index 7c2c95214149eb..6f60b52c63b8ec 100644 --- a/tools/mkdocs/overrides/main.html +++ b/tools/mkdocs/overrides/main.html @@ -6,6 +6,6 @@ {% block announce %}

- We released Renovate v39. Read the Release notes for major versions of Renovate to learn what's changed. + If you use a Mend-hosted Renovate App and you have encrypted secrets in your Renovate config, then you must migrate the secrets to the Developer Portal UI. If you do not migrate the secrets, eventually Renovate will stop running on repositories that use the Mend-hosted Renovate App. Please read the announcement about the disabling of Encrypted Secrets in Mend-hosted Renovate apps to learn more.

{% endblock %} diff --git a/tools/test-shards.ts b/tools/test-shards.ts new file mode 100644 index 00000000000000..0de5287c4a5d8d --- /dev/null +++ b/tools/test-shards.ts @@ -0,0 +1,145 @@ +import crypto from 'node:crypto'; +import { minimatch } from 'minimatch'; +import { testShards } from './test/shards'; +import type { RunsOn, ShardGroup } from './test/types'; + +/** + * Given the file list affected by commit, return the list + * of shards that test these changes. + */ +function getMatchingShards(files: string[]): string[] { + const matchingShards = new Set(); + for (const file of files) { + for (const [key, { matchPaths }] of Object.entries(testShards)) { + const patterns = matchPaths.map((path) => + path.endsWith('.spec.ts') + ? path.replace(/\.spec\.ts$/, '{.ts,.spec.ts}') + : `${path}/**/*`, + ); + + if (patterns.some((pattern) => minimatch(file, pattern))) { + matchingShards.add(key); + break; + } + } + } + + return Object.keys(testShards).filter((shard) => matchingShards.has(shard)); +} + +/** + * Distribute items evenly across runner instances. + */ +function scheduleItems(items: T[], availableInstances: number): T[][] { + const numInstances = Math.min(items.length, availableInstances); + const maxPerInstance = Math.ceil(items.length / numInstances); + const lighterInstancesIdx = + items.length % numInstances === 0 + ? numInstances + : items.length % numInstances; + + const partitionSizes = Array.from({ length: numInstances }, (_, idx) => + idx < lighterInstancesIdx ? maxPerInstance : maxPerInstance - 1, + ); + + const result: T[][] = Array.from({ length: numInstances }, () => []); + let rest = items.slice(); + for (let idx = 0; idx < numInstances; idx += 1) { + const partitionSize = partitionSizes[idx]; + const partition = rest.slice(0, partitionSize); + result[idx] = partition; + rest = rest.slice(partitionSize); + } + + return result; +} + +let shardKeys = Object.keys(testShards); + +if (process.env.FILTER_SHARDS === 'true' && process.env.CHANGED_FILES) { + try { + const changedFiles: string[] = JSON.parse(process.env.CHANGED_FILES); + const matchingShards = getMatchingShards(changedFiles); + if (matchingShards.length === 0) { + // eslint-disable-next-line no-console + console.log(`test-matrix-empty=true`); + process.exit(0); + } + shardKeys = shardKeys.filter((key) => matchingShards.includes(key)); + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + process.exit(1); + } +} + +/** + * Not all runners are created equal. + * Minutes cost proportion is 1:2:10 for Ubuntu:Windows:MacOS. + * + * Although it's free in our case, + * we can't run as many Windows and MacOS runners as we want. + * + * Because of this, we partition shards into groups, given that: + * - There are 16 shards in total + * - We can't run more than 10 Windows runners + * - We can't run more than 5 MacOS runners + */ +const shardGrouping: Record = { + 'ubuntu-latest': scheduleItems(shardKeys, 16), +}; + +if (process.env.ALL_PLATFORMS === 'true') { + // shardGrouping['windows-latest'] = scheduleItems(shardKeys, 8); + shardGrouping['macos-latest'] = scheduleItems(shardKeys, 4); +} + +const shardGroups: ShardGroup[] = []; +for (const [os, groups] of Object.entries(shardGrouping)) { + const coverage = os === 'ubuntu-latest'; + + const total = groups.length; + for (let idx = 0; idx < groups.length; idx += 1) { + const number = idx + 1; + const platform = os.replace(/-latest$/, ''); + const name = + platform === 'ubuntu' + ? `test (${number}/${total})` + : `test-${platform} (${number}/${total})`; + + const shards = groups[idx]; + const cacheKey = crypto + .createHash('md5') + .update(shards.join(':')) + .digest('hex'); + + const runnerTimeoutMinutes = + { + ubuntu: 10, + windows: 20, + macos: 20, + }[platform] ?? 20; + + const testTimeoutMilliseconds = + { + windows: 240000, + }[platform] ?? 120000; + + shardGroups.push({ + os: os as RunsOn, + coverage, + name, + shards: shards.join(' '), + 'cache-key': cacheKey, + 'runner-timeout-minutes': runnerTimeoutMinutes, + 'test-timeout-milliseconds': testTimeoutMilliseconds, + 'upload-artifact-name': `coverage-${shards.sort().join('_')}`, + }); + } +} + +/** + * Output will be consumed by `setup` CI job. + */ +// eslint-disable-next-line no-console +console.log(`test-shard-matrix=${JSON.stringify(shardGroups)}`); diff --git a/tools/test/shards.ts b/tools/test/shards.ts new file mode 100644 index 00000000000000..ddeda1bc4ba135 --- /dev/null +++ b/tools/test/shards.ts @@ -0,0 +1,74 @@ +import type { ShardConfig } from './types'; + +/** + * Configuration for test shards that can be run with `TEST_SHARD` environment variable. + * + * For each shard, we specify a subset of tests to run. + * The tests from previous shards are excluded from the next shard. + * + * Storing shards config in the separate file helps to form CI matrix + * using pre-installed `jq` utility. + */ +export const testShards: Record = { + 'datasources-1': { + matchPaths: ['lib/modules/datasource/[a-g]*'], + }, + 'datasources-2': { + matchPaths: ['lib/modules/datasource'], + }, + 'managers-1': { + matchPaths: ['lib/modules/manager/[a-c]*'], + }, + 'managers-2': { + matchPaths: ['lib/modules/manager/[d-h]*'], + }, + 'managers-3': { + matchPaths: ['lib/modules/manager/[i-n]*'], + }, + 'managers-4': { + matchPaths: ['lib/modules/manager'], + }, + platform: { + matchPaths: ['lib/modules/platform'], + }, + versioning: { + matchPaths: ['lib/modules/versioning'], + }, + 'workers-1': { + matchPaths: [ + 'lib/workers/repository/changelog', + 'lib/workers/repository/config-migration', + 'lib/workers/repository/extract', + 'lib/workers/repository/finalize', + 'lib/workers/repository/init', + 'lib/workers/repository/model', + ], + }, + 'workers-2': { + matchPaths: [ + 'lib/workers/repository/onboarding', + 'lib/workers/repository/process', + ], + }, + 'workers-3': { + matchPaths: [ + 'lib/workers/repository/update', + 'lib/workers/repository/updates', + ], + }, + 'workers-4': { + matchPaths: ['lib/workers'], + }, + 'git-1': { + matchPaths: ['lib/util/git/index.spec.ts'], + }, + 'git-2': { + matchPaths: ['lib/util/git'], + }, + util: { + matchPaths: ['lib/util'], + }, + other: { + matchPaths: ['lib'], + }, +}; diff --git a/tools/test/types.ts b/tools/test/types.ts new file mode 100644 index 00000000000000..49834e35650909 --- /dev/null +++ b/tools/test/types.ts @@ -0,0 +1,83 @@ +import type { JestConfigWithTsJest } from 'ts-jest'; + +/** + * Configuration for single test shard. + */ +export interface ShardConfig { + /** + * Path patterns to match against the test file paths, of two types: + * + * 1. Particular file, e.g. `lib/util/git/index.spec.ts` + * + * - File pattern MUST end with `.spec.ts` + * - This will only search for the particular test file + * - It enables coverage for the `*.ts` file with the same name, + * e.g. `lib/util/git/index.ts` + * - You probably want to use directory pattern instead + * + * 2. Whole directory, e.g. `lib/modules/datasource` + * + * - This will search for all `*.spec.ts` files under the directory + * - It enables coverage all `*.ts` files under the directory, + * e.g. `lib/modules/datasource/foo/bar/baz.ts` + */ + matchPaths: string[]; +} + +export type RunsOn = 'ubuntu-latest' | 'windows-latest' | 'macos-latest'; + +export interface ShardGroup { + /** + * Input for `runs-on` field. + */ + os: RunsOn; + + /** + * Controls whether coverage is collected for this shard group. + */ + coverage: boolean; + + /** + * Input for `name` field. + */ + name: string; + + /** + * Space-separated list of shard keys, it's + * meant to be inserted into bash for-loop. + */ + shards: string; + + /** + * It's meant to be used for Jest caching. + */ + 'cache-key': string; + + /** + * It's used to set test runner timeout. + */ + 'runner-timeout-minutes': number; + + /** + * It's used to set `--test-timeout` Jest CLI flag. + */ + 'test-timeout-milliseconds': number; + + /** + * It's used as the name for coverage artifact. + */ + 'upload-artifact-name': string; +} + +export type JestConfig = JestConfigWithTsJest & { + // https://github.com/renovatebot/renovate/issues/17034 + workerIdleMemoryLimit?: string; +}; + +/** + * Subset of Jest config that is relevant for sharded test run. + */ +export type JestShardedSubconfig = Pick< + JestConfig, + 'testMatch' | 'coverageDirectory' +>; diff --git a/tools/test/utils.ts b/tools/test/utils.ts new file mode 100644 index 00000000000000..c80f597a0b6cee --- /dev/null +++ b/tools/test/utils.ts @@ -0,0 +1,11 @@ +import { env } from 'process'; + +export 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; +}